Merge branch 'devops/black' into 'dev'

🏴Black Formatting

See merge request crafty-controller/crafty-commander!223
This commit is contained in:
Iain Powrie 2022-03-24 18:52:52 +00:00
commit ff1d94505b
78 changed files with 5825 additions and 3731 deletions

View File

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

View File

@ -1,14 +1,57 @@
# Crafty Controller 4.0 - Lint & Build Pipes
# [Maintainer: Zedifus(https://gitlab.com/Zedifus)]
###################################################
# yamllint disable rule:line-length
---
stages:
- test
- prod-deployment
- dev-deployment
- lint
- prod-deployment
- dev-deployment
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
yamllint:
stage: lint
image: registry.gitlab.com/pipeline-components/yamllint:latest
tags:
- 'docker'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never
script:
- yamllint .
jsonlint:
stage: lint
image: registry.gitlab.com/pipeline-components/jsonlint:latest
tags:
- 'docker'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never
script:
- |
find . -not -path './.git/*' -name '*.json' -type f -print0 |
parallel --will-cite -k -0 -n1 jsonlint -q
black:
stage: lint
image: registry.gitlab.com/pipeline-components/black:latest
tags:
- 'docker'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never
script:
- black --check --verbose -- .
pylint:
stage: test
stage: lint
image: python:3.7-slim
tags:
- 'docker'
@ -124,81 +167,82 @@ docker-build-prod:
win-dev-build:
stage: dev-deployment
tags:
- win64
- win64
cache:
paths:
- .venv/
rules:
- if: "$CI_COMMIT_BRANCH == 'dev'"
- 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
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz
--collect-all six
- |
$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
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz
--collect-all six
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
artifacts:
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
paths:
- app\
- .\crafty_commander.exe
- app\
- .\crafty_commander.exe
exclude:
- app\classes\**\*
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build
- app\classes\**\*
win-prod-build:
stage: prod-deployment
tags:
- win64
- win64
cache:
paths:
- .venv/
rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
- 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
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz
--collect-all six
- |
$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
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz
--collect-all six
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
artifacts:
name: "crafty-${CI_RUNNER_TAGS}-${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
paths:
- app\
- .\crafty_commander.exe
- app\
- .\crafty_commander.exe
exclude:
- app\classes\**\*
# Download latest:
# | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/master/download?job=win-prod-build
- app\classes\**\*

View File

@ -78,7 +78,9 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=abstract-method,
disable=C0330,
C0326,
abstract-method,
attribute-defined-outside-init,
bad-inline-option,
bare-except,
@ -306,7 +308,7 @@ indent-after-paren=4
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=150
max-line-length=88
# Maximum number of lines in a module.
max-module-lines=2000

View File

@ -1,3 +1,11 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue)](https://gitlab.com/crafty-controller/crafty-commander)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.0--alpha3.5-orange)](https://gitlab.com/crafty-controller/crafty-commander)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-commander)
[![Build Status](https://gitlab.com/crafty-controller/crafty-commander/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-commander/-/commits/master)
# Crafty Controller 4.0.0-alpha.3.5
> Python based Control Panel for your Minecraft Server

View File

@ -1,12 +1,15 @@
import logging
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
from app.classes.models.crafty_permissions import (
crafty_permissions,
Enum_Permissions_Crafty,
)
from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__)
class Crafty_Perms_Controller:
class Crafty_Perms_Controller:
@staticmethod
def list_defined_crafty_permissions():
permissions_list = crafty_permissions.get_permissions_list()
@ -18,24 +21,34 @@ class Crafty_Perms_Controller:
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)
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)
return crafty_permissions.can_add_in_crafty(
user_id, Enum_Permissions_Crafty.Server_Creation
)
@staticmethod
def can_add_user(): # Add back argument 'user_id' when you work on this
#TODO: Complete if we need a User Addition limit
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config)
def can_add_user(): # Add back argument 'user_id' when you work on this
return True
# TODO: Complete if we need a User Addition limit
# return crafty_permissions.can_add_in_crafty(
# user_id, Enum_Permissions_Crafty.User_Config
# )
@staticmethod
def can_add_role(): # Add back argument 'user_id' when you work on this
#TODO: Complete if we need a Role Addition limit
#return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config)
def can_add_role(): # Add back argument 'user_id' when you work on this
return True
# TODO: Complete if we need a Role Addition limit
# return crafty_permissions.can_add_in_crafty(
# user_id, Enum_Permissions_Crafty.Roles_Config
# )
@staticmethod
def list_all_crafty_permissions_quantity_limits():

View File

@ -5,18 +5,19 @@ from app.classes.models.servers import servers_helper
logger = logging.getLogger(__name__)
class Management_Controller:
#************************************************************************************************
# **********************************************************************************
# Host_Stats Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_latest_hosts_stats():
return management_helper.get_latest_hosts_stats()
#************************************************************************************************
# **********************************************************************************
# Commands Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_unactioned_commands():
return management_helper.get_unactioned_commands()
@ -26,43 +27,61 @@ class Management_Controller:
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, f"issued command {command} for server {server_name}", server_id, remote_ip)
management_helper.add_to_audit_log(
user_id,
f"issued command {command} for server {server_name}",
server_id,
remote_ip,
)
management_helper.add_command(server_id, user_id, remote_ip, command)
@staticmethod
def mark_command_complete(command_id=None):
return management_helper.mark_command_complete(command_id)
#************************************************************************************************
# **********************************************************************************
# Audit_Log Methods
#************************************************************************************************
# **********************************************************************************
@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)
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)
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):
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
)
server_id,
action,
interval,
interval_type,
start_time,
command,
comment,
enabled,
)
@staticmethod
def delete_scheduled_task(schedule_id):
@ -96,16 +115,24 @@ class Management_Controller:
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, excluded_dirs: list = None, compress: bool = False,):
return management_helper.set_backup_config(server_id, backup_path, max_backups, excluded_dirs, compress)
def set_backup_config(
server_id: int,
backup_path: str = None,
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
):
return management_helper.set_backup_config(
server_id, backup_path, max_backups, excluded_dirs, compress
)
@staticmethod
def get_excluded_backup_dirs(server_id: int):

View File

@ -7,11 +7,11 @@ from app.classes.shared.helpers import helper
logger = logging.getLogger(__name__)
class Roles_Controller:
class Roles_Controller:
@staticmethod
def get_all_roles():
return roles_helper.get_all_roles()
return roles_helper.get_all_roles()
@staticmethod
def get_roleid_by_name(role_name):
@ -21,9 +21,8 @@ class Roles_Controller:
def get_role(role_id):
return roles_helper.get_role(role_id)
@staticmethod
def update_role(role_id: str, role_data = None, permissions_mask: str = "00000000"):
def update_role(role_id: str, role_data=None, permissions_mask: str = "00000000"):
if role_data is None:
role_data = {}
base_data = Roles_Controller.get_role_with_servers(role_id)
@ -34,17 +33,20 @@ class Roles_Controller:
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'])
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(f"role: {role_data} +server:{added_servers} -server{removed_servers}")
up_data["last_update"] = helper.get_time_as_string()
logger.debug(
f"role: {role_data} +server:{added_servers} -server{removed_servers}"
)
for server in added_servers:
server_permissions.get_or_create(role_id, server, permissions_mask)
for server in base_data['servers']:
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
# 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)
@ -56,7 +58,7 @@ class Roles_Controller:
@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'])
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)
@ -74,9 +76,9 @@ class Roles_Controller:
servers = set()
for s in servers_query:
servers.add(s.server_id.server_id)
role['servers'] = servers
#logger.debug("role: ({}) {}".format(role_id, role))
role["servers"] = servers
# logger.debug("role: ({}) {}".format(role_id, role))
return role
else:
#logger.debug("role: ({}) {}".format(role_id, {}))
# logger.debug("role: ({}) {}".format(role_id, {}))
return {}

View File

@ -1,6 +1,9 @@
import logging
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.models.server_permissions import (
server_permissions,
Enum_Permissions_Server,
)
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.roles import roles_helper
from app.classes.models.servers import servers_helper
@ -8,8 +11,8 @@ from app.classes.shared.main_models import db_helper
logger = logging.getLogger(__name__)
class Server_Perms_Controller:
class Server_Perms_Controller:
@staticmethod
def get_server_user_list(server_id):
return server_permissions.get_server_user_list(server_id)
@ -42,20 +45,28 @@ class Server_Perms_Controller:
role_list = server_permissions.get_server_roles(old_server_id)
for role in role_list:
server_permissions.add_role_server(
new_server_id, role.role_id,
server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id)))
#server_permissions.add_role_server(new_server_id, role.role_id, '00001000')
new_server_id,
role.role_id,
server_permissions.get_permissions_mask(
int(role.role_id), int(old_server_id)
),
)
# server_permissions.add_role_server(new_server_id, role.role_id,"00001000")
#************************************************************************************************
# **********************************************************************************
# Servers Permissions Methods
#************************************************************************************************
# **********************************************************************************
@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)
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):
@ -86,7 +97,9 @@ class Server_Perms_Controller:
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'))
role_test = server_permissions.get_role_servers_from_role_id(
r.get("role_id")
)
for t in role_test:
role_server.append(t)
@ -94,6 +107,8 @@ class Server_Perms_Controller:
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]})
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

@ -5,17 +5,21 @@ import json
from app.classes.controllers.roles_controller import Roles_Controller
from app.classes.models.servers import servers_helper
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.models.server_permissions import (
server_permissions,
Enum_Permissions_Server,
)
from app.classes.shared.helpers import helper
from app.classes.shared.main_models import db_helper
logger = logging.getLogger(__name__)
class Servers_Controller:
#************************************************************************************************
# **********************************************************************************
# Generic Servers Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def create_server(
name: str,
@ -27,7 +31,8 @@ class Servers_Controller:
server_log_file: str,
server_stop: str,
server_type: str,
server_port=25565):
server_port=25565,
):
return servers_helper.create_server(
name,
server_uuid,
@ -38,7 +43,8 @@ class Servers_Controller:
server_log_file,
server_stop,
server_type,
server_port)
server_port,
)
@staticmethod
def get_server_obj(server_id):
@ -66,8 +72,8 @@ class Servers_Controller:
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'])
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)
@ -75,9 +81,9 @@ class Servers_Controller:
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()
@ -100,17 +106,26 @@ class Servers_Controller:
@staticmethod
def get_authorized_servers_stats_api_key(api_key: ApiKeys):
server_data = []
authorized_servers = Servers_Controller.get_authorized_servers(api_key.user.user_id)
authorized_servers = Servers_Controller.get_authorized_servers(
api_key.user.user_id
)
for s in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
key_permissions = server_permissions.get_api_key_permissions_list(api_key, s.get('server_id'))
latest = servers_helper.get_latest_server_stats(s.get("server_id"))
key_permissions = server_permissions.get_api_key_permissions_list(
api_key, s.get("server_id")
)
if Enum_Permissions_Server.Commands in key_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0],
"user_command_permission": user_command_permission})
server_data.append(
{
"server_data": s,
"stats": db_helper.return_rows(latest)[0],
"user_command_permission": user_command_permission,
}
)
return server_data
@staticmethod
@ -119,18 +134,22 @@ class Servers_Controller:
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'))
latest = servers_helper.get_latest_server_stats(s.get("server_id"))
# TODO
user_permissions = server_permissions.get_user_id_permissions_list(user_id, s.get('server_id'))
user_permissions = server_permissions.get_user_id_permissions_list(
user_id, s.get("server_id")
)
if Enum_Permissions_Server.Commands in user_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({
'server_data': s,
'stats': db_helper.return_rows(latest)[0],
'user_command_permission': user_command_permission
})
server_data.append(
{
"server_data": s,
"stats": db_helper.return_rows(latest)[0],
"user_command_permission": user_command_permission,
}
)
return server_data
@ -138,9 +157,9 @@ class Servers_Controller:
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)
@ -157,7 +176,9 @@ class Servers_Controller:
def server_id_authorized(server_id_a, user_id):
user_roles = users_helper.user_role_query(user_id)
for role in user_roles:
for server_id_b in server_permissions.get_role_servers_from_role_id(role.role_id):
for server_id_b in server_permissions.get_role_servers_from_role_id(
role.role_id
):
if str(server_id_a) == str(server_id_b.server_id):
return True
return False
@ -197,21 +218,23 @@ class Servers_Controller:
def get_update_status(server_id):
return servers_helper.get_update_status(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')
server_path = stats["server_id"]["path"]
path = os.path.join(server_path, "banned-players.json")
try:
with open(helper.get_os_understandable_path(path), encoding='utf-8') as file:
with open(
helper.get_os_understandable_path(path), encoding="utf-8"
) as file:
content = file.read()
file.close()
except Exception as ex:
print (ex)
print(ex)
return None
return json.loads(content)
@ -219,18 +242,20 @@ class Servers_Controller:
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'])
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)
))
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 helper.check_file_exists(log_file_path) and \
helper.is_file_older_than_x_days(log_file_path, logs_delete_after):
if helper.check_file_exists(
log_file_path
) and helper.is_file_older_than_x_days(
log_file_path, logs_delete_after
):
os.remove(log_file_path)

View File

@ -2,17 +2,21 @@ import logging
from typing import Optional
from app.classes.models.users import users_helper
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
from app.classes.models.crafty_permissions import (
crafty_permissions,
Enum_Permissions_Crafty,
)
from app.classes.shared.helpers import helper
from app.classes.shared.authentication import authentication
logger = logging.getLogger(__name__)
class Users_Controller:
#************************************************************************************************
# **********************************************************************************
# Users Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_all_users():
return users_helper.get_all_users()
@ -59,26 +63,31 @@ class Users_Controller:
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'])
added_roles = user_data["roles"].difference(base_data["roles"])
removed_roles = base_data["roles"].difference(user_data["roles"])
elif key == "password":
if user_data['password'] is not None and user_data['password'] != "":
up_data['password'] = helper.encode_pass(user_data['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']
up_data["last_update"] = helper.get_time_as_string()
up_data["lang"] = user_data["lang"]
logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}")
for role in added_roles:
users_helper.get_or_create(user_id=user_id, role_id=role)
permissions_mask = user_crafty_data.get('permissions_mask', '000')
permissions_mask = user_crafty_data.get("permissions_mask", "000")
if 'server_quantity' in user_crafty_data:
limit_server_creation = user_crafty_data['server_quantity'][
Enum_Permissions_Crafty.Server_Creation.name]
if "server_quantity" in user_crafty_data:
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]
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
@ -89,19 +98,44 @@ class Users_Controller:
permissions_mask,
limit_server_creation,
limit_user_creation,
limit_role_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, email="default@example.com", enabled: bool = True, superuser: bool = False):
return users_helper.add_user(username, password=password, email=email, enabled=enabled, superuser=superuser)
def add_user(
username,
password,
email="default@example.com",
enabled: bool = True,
superuser: bool = False,
):
return users_helper.add_user(
username,
password=password,
email=email,
enabled=enabled,
superuser=superuser,
)
@staticmethod
def add_rawpass_user(username, password, email="default@example.com", enabled: bool = True, superuser: bool = False):
return users_helper.add_rawpass_user(username, password=password, email=email, enabled=enabled, superuser=superuser)
def add_rawpass_user(
username,
password,
email="default@example.com",
enabled: bool = True,
superuser: bool = False,
):
return users_helper.add_rawpass_user(
username,
password=password,
email=email,
enabled=enabled,
superuser=superuser,
)
@staticmethod
def remove_user(user_id):
@ -122,16 +156,16 @@ class Users_Controller:
@staticmethod
def get_user_id_by_api_token(token: str) -> str:
token_data = authentication.check_no_iat(token)
return token_data['user_id']
return token_data["user_id"]
@staticmethod
def get_user_by_api_token(token: str):
_, user = authentication.check(token)
return user
# ************************************************************************************************
# **********************************************************************************
# User Roles Methods
# ************************************************************************************************
# **********************************************************************************
@staticmethod
def get_user_roles_id(user_id):
@ -153,9 +187,9 @@ class Users_Controller:
def user_role_query(user_id):
return users_helper.user_role_query(user_id)
# ************************************************************************************************
# **********************************************************************************
# Api Keys Methods
# ************************************************************************************************
# **********************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
@ -166,10 +200,16 @@ class Users_Controller:
return users_helper.get_user_api_key(key_id)
@staticmethod
def add_user_api_key(name: str, user_id: str, superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None):
return users_helper.add_user_api_key(name, user_id, superuser, server_permissions_mask, crafty_permissions_mask)
def add_user_api_key(
name: str,
user_id: str,
superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None,
):
return users_helper.add_user_api_key(
name, user_id, superuser, server_permissions_mask, crafty_permissions_mask
)
@staticmethod
def delete_user_api_keys(user_id: str):

View File

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

View File

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

View File

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

View File

@ -18,8 +18,8 @@ try:
except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
class ServerJars:
class ServerJars:
def __init__(self):
self.base_url = "https://serverjars.com"
@ -41,8 +41,8 @@ class ServerJars:
logger.error(f"Unable to parse serverjar.com api result due to error: {e}")
return {}
api_result = api_data.get('status')
api_response = api_data.get('response', {})
api_result = api_data.get("status")
api_response = api_data.get("response", {})
if api_result != "success":
logger.error(f"Api returned a failed status: {api_result}")
@ -55,7 +55,7 @@ class ServerJars:
cache_file = helper.serverjar_cache
cache = {}
try:
with open(cache_file, "r", encoding='utf-8') as f:
with open(cache_file, "r", encoding="utf-8") as f:
cache = json.load(f)
except Exception as e:
@ -65,7 +65,7 @@ class ServerJars:
def get_serverjar_data(self):
data = self._read_cache()
return data.get('servers')
return data.get("servers")
def get_serverjar_data_sorted(self):
data = self.get_serverjar_data()
@ -80,10 +80,10 @@ class ServerJars:
try:
return int(x)
except ValueError:
temp = x.split('-')
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('.')]
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)
@ -125,10 +125,7 @@ class ServerJars:
if cache_old:
logger.info("Cache file is over 1 day old, refreshing")
now = datetime.now()
data = {
'last_refreshed': now.strftime("%m/%d/%Y, %H:%M:%S"),
'servers': {}
}
data = {"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), "servers": {}}
jar_types = self._get_server_type_list()
@ -140,52 +137,54 @@ class ServerJars:
# jar versions for this server
versions = self._get_jar_details(s)
# add these versions (a list) to the dict with a key of the server type
data['servers'].update({
s: versions
})
# add these versions (a list) to the dict with
# a key of the server type
data["servers"].update({s: versions})
# save our cache
try:
with open(cache_file, "w", encoding='utf-8') as f:
with open(cache_file, "w", encoding="utf-8") as f:
f.write(json.dumps(data, indent=4))
logger.info("Cache file refreshed")
except Exception as e:
logger.error(f"Unable to update serverjars.com cache file: {e}")
def _get_jar_details(self, jar_type='servers'):
url = f'/api/fetchAll/{jar_type}'
def _get_jar_details(self, jar_type="servers"):
url = f"/api/fetchAll/{jar_type}"
response = self._get_api_result(url)
temp = []
for v in response:
temp.append(v.get('version'))
time.sleep(.5)
temp.append(v.get("version"))
time.sleep(0.5)
return temp
def _get_server_type_list(self):
url = '/api/fetchTypes/'
url = "/api/fetchTypes/"
response = self._get_api_result(url)
return response
def download_jar(self, server, version, path, server_id):
update_thread = threading.Thread(target=self.a_download_jar, daemon=True, args=(server, version, path, server_id))
update_thread = threading.Thread(
target=self.a_download_jar,
daemon=True,
args=(server, version, path, server_id),
)
update_thread.start()
def a_download_jar(self, server, version, path, server_id):
#delaying download for server register to finish
# delaying download for server register to finish
time.sleep(3)
fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}"
server_users = server_permissions.get_server_user_list(server_id)
#We need to make sure the server is registered before we submit a db update for it's stats.
# We need to make sure the server is registered before
# we submit a db update for it's stats.
while True:
try:
Servers_Controller.set_download(server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
websocket_helper.broadcast_user(user, "send_start_reload", {})
break
except:
@ -194,25 +193,27 @@ class ServerJars:
# open a file stream
with requests.get(fetch_url, timeout=2, stream=True) as r:
try:
with open(path, 'wb') as output:
with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output)
Servers_Controller.finish_download(server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable download finished")
websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
websocket_helper.broadcast_user(user, "send_start_reload", {})
return True
except Exception as e:
logger.error(f"Unable to save jar to {path} due to error:{e}")
Servers_Controller.finish_download(server_id)
server_users = server_permissions.get_server_user_list(server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable download finished")
websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
websocket_helper.broadcast_user(user, "send_start_reload", {})
return False

View File

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

View File

@ -5,25 +5,32 @@ from app.classes.shared.permission_helper import permission_helper
from app.classes.models.users import Users, ApiKeys
try:
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DoesNotExist
from peewee import (
SqliteDatabase,
Model,
ForeignKeyField,
CharField,
IntegerField,
DoesNotExist,
)
from enum import Enum
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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')
user_id = ForeignKeyField(Users, backref="users_crafty")
permissions = CharField(default="00000000")
limit_server_creation = IntegerField(default=-1)
limit_user_creation = IntegerField(default=0)
@ -33,22 +40,23 @@ class User_Crafty(Model):
created_role = IntegerField(default=0)
class Meta:
table_name = 'user_crafty'
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:
#************************************************************************************************
class Permissions_Crafty:
# **********************************************************************************
# Crafty Permissions Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_permissions_list():
permissions_list = []
@ -67,15 +75,17 @@ class Permissions_Crafty:
@staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Crafty):
result = False
if permission_mask[permission_tested.value] == '1':
if permission_mask[permission_tested.value] == "1":
result = True
return result
@staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Crafty, value):
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)
permission_mask = "".join(l)
return permission_mask
@staticmethod
@ -84,7 +94,7 @@ class Permissions_Crafty:
@staticmethod
def get_crafty_permissions_mask(user_id):
permissions_mask = ''
permissions_mask = ""
user_crafty = crafty_permissions.get_User_Crafty(user_id)
permissions_mask = user_crafty.permissions
return permissions_mask
@ -102,55 +112,71 @@ class Permissions_Crafty:
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.Server_Creation.name: user_crafty.limit_server_creation, # pylint: disable=line-too-long
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()
user_crafty = (
User_Crafty.select().where(User_Crafty.user_id == user_id).get()
)
except 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 = 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()
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):
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 = (
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()
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):
@ -173,7 +199,10 @@ class Permissions_Crafty:
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 )
return can and (
(quantity_list[permission.name] < limit_list[permission.name])
or limit_list[permission.name] == -1
)
@staticmethod
def add_server_creation(user_id):
@ -188,12 +217,15 @@ class Permissions_Crafty:
if user.superuser and key.superuser:
return crafty_permissions.get_permissions_list()
else:
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(user.user_id)
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(
user.user_id
)
key_permissions_mask: str = key.crafty_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_mask = permission_helper.combine_masks(
user_permissions_mask, key_permissions_mask
)
permissions_list = crafty_permissions.get_permissions(permissions_mask)
return permissions_list
crafty_permissions = Permissions_Crafty()

View File

@ -9,38 +9,51 @@ from app.classes.shared.main_models import db_helper
from app.classes.web.websocket_helper import websocket_helper
try:
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DateTimeField, FloatField, TextField, AutoField, BooleanField
from peewee import (
SqliteDatabase,
Model,
ForeignKeyField,
CharField,
IntegerField,
DateTimeField,
FloatField,
TextField,
AutoField,
BooleanField,
)
from playhouse.shortcuts import model_to_dict
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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='')
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="")
@ -58,16 +71,16 @@ class Host_Stats(Model):
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='')
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:
@ -75,9 +88,9 @@ class Commands(Model):
database = database
#************************************************************************************************
# **********************************************************************************
# Webhooks Class
#************************************************************************************************
# **********************************************************************************
class Webhooks(Model):
id = AutoField()
name = CharField(max_length=64, unique=True, index=True)
@ -91,12 +104,12 @@ class Webhooks(Model):
database = database
#************************************************************************************************
# **********************************************************************************
# Schedules Class
#************************************************************************************************
# **********************************************************************************
class Schedules(Model):
schedule_id = IntegerField(unique=True, primary_key=True)
server_id = ForeignKeyField(Servers, backref='schedule_server')
server_id = ForeignKeyField(Servers, backref="schedule_server")
enabled = BooleanField()
action = CharField()
interval = IntegerField()
@ -110,44 +123,48 @@ class Schedules(Model):
delay = IntegerField(default=0)
class Meta:
table_name = 'schedules'
table_name = "schedules"
database = database
#************************************************************************************************
# **********************************************************************************
# Backups Class
#************************************************************************************************
# **********************************************************************************
class Backups(Model):
excluded_dirs = CharField(null=True)
max_backups = IntegerField()
server_id = ForeignKeyField(Servers, backref='backups_server')
server_id = ForeignKeyField(Servers, backref="backups_server")
compress = BooleanField(default=False)
class Meta:
table_name = 'backups'
table_name = "backups"
database = database
class helpers_management:
#************************************************************************************************
# **********************************************************************************
# Host_Stats Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_latest_hosts_stats():
#pylint: disable=no-member
# pylint: disable=no-member
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()
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():
@ -158,13 +175,13 @@ class helpers_management:
def mark_command_complete(command_id=None):
if command_id is not None:
logger.debug(f"Marking Command {command_id} completed")
Commands.update({
Commands.executed: True
}).where(Commands.command_id == command_id).execute()
Commands.update({Commands.executed: True}).where(
Commands.command_id == command_id
).execute()
#************************************************************************************************
# **********************************************************************************
# Audit_Log Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_actity_log():
q = Audit_Log.select()
@ -179,22 +196,24 @@ class helpers_management:
server_users = server_permissions.get_server_user_list(server_id)
for user in server_users:
websocket_helper.broadcast_user(user,'notification', audit_msg)
websocket_helper.broadcast_user(user, "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()
#deletes records when they're more than 100
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()
# deletes records when they're more than 100
ordered = Audit_Log.select().order_by(+Audit_Log.created)
for item in ordered:
if not helper.get_setting('max_audit_entries'):
if not helper.get_setting("max_audit_entries"):
max_entries = 300
else:
max_entries = helper.get_setting('max_audit_entries')
max_entries = helper.get_setting("max_audit_entries")
if Audit_Log.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
else:
@ -202,28 +221,31 @@ class helpers_management:
@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()
#deletes records when they're more than 100
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()
# deletes records when they're more than 100
ordered = Audit_Log.select().order_by(+Audit_Log.created)
for item in ordered:
#configurable through app/config/config.json
if not helper.get_setting('max_audit_entries'):
# configurable through app/config/config.json
if not helper.get_setting("max_audit_entries"):
max_entries = 300
else:
max_entries = helper.get_setting('max_audit_entries')
max_entries = helper.get_setting("max_audit_entries")
if Audit_Log.select().count() > max_entries:
Audit_Log.delete().where(Audit_Log.audit_id == item.audit_id).execute()
else:
return
#************************************************************************************************
# **********************************************************************************
# Schedules Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def create_scheduled_task(
server_id,
@ -235,24 +257,26 @@ class helpers_management:
comment=None,
enabled=True,
one_time=False,
cron_string='* * * * *',
cron_string="* * * * *",
parent=None,
delay=0):
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,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string,
Schedules.parent: parent,
Schedules.delay: delay
}).execute()
delay=0,
):
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,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string,
Schedules.parent: parent,
Schedules.delay: delay,
}
).execute()
return sch_id
@staticmethod
@ -282,7 +306,11 @@ class helpers_management:
@staticmethod
def get_child_schedules_by_server(schedule_id, server_id):
return Schedules.select().where(Schedules.server_id == server_id, Schedules.parent == schedule_id).execute()
return (
Schedules.select()
.where(Schedules.server_id == server_id, Schedules.parent == schedule_id)
.execute()
)
@staticmethod
def get_child_schedules(schedule_id):
@ -294,22 +322,24 @@ class helpers_management:
@staticmethod
def get_schedules_enabled():
#pylint: disable=singleton-comparison
# pylint: disable=singleton-comparison
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(Servers)[0]
row = (
Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
)
conf = {
"backup_path": row.server_id.backup_path,
"excluded_dirs": row.excluded_dirs,
"max_backups": row.max_backups,
"server_id": row.server_id.server_id,
"compress": row.compress
"compress": row.compress,
}
except IndexError:
conf = {
@ -322,7 +352,13 @@ class helpers_management:
return conf
@staticmethod
def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None, compress: bool = False):
def set_backup_config(
server_id: int,
backup_path: str = None,
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
):
logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).count() != 0:
new_row = False
@ -331,34 +367,42 @@ class helpers_management:
conf = {
"excluded_dirs": None,
"max_backups": 0,
"server_id": server_id,
"compress": False
"server_id": server_id,
"compress": False,
}
new_row = True
if max_backups is not None:
conf['max_backups'] = max_backups
conf["max_backups"] = max_backups
if excluded_dirs is not None:
dirs_to_exclude = ",".join(excluded_dirs)
conf['excluded_dirs'] = dirs_to_exclude
conf['compress'] = compress
conf["excluded_dirs"] = dirs_to_exclude
conf["compress"] = compress
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()
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()
u2 = (
Backups.update(conf).where(Backups.server_id == server_id).execute()
)
logger.debug(f"Updating existing backup record. {u1}+{u2} rows affected")
else:
with database.atomic():
conf["server_id"] = server_id
if backup_path is not None:
Servers.update(backup_path=backup_path).where(Servers.server_id == server_id)
Servers.update(backup_path=backup_path).where(
Servers.server_id == server_id
)
Backups.create(**conf)
logger.debug("Creating new backup record.")
def get_excluded_backup_dirs(self, server_id: int):
excluded_dirs = self.get_backup_config(server_id)['excluded_dirs']
excluded_dirs = self.get_backup_config(server_id)["excluded_dirs"]
if excluded_dirs is not None and excluded_dirs != "":
dir_list = excluded_dirs.split(",")
else:
@ -372,7 +416,10 @@ class helpers_management:
excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else:
logger.debug(f"Not adding {dir_to_add} to excluded directories - already in the excluded directory list for server ID {server_id}")
logger.debug(
f"Not adding {dir_to_add} to excluded directories - "
f"already in the excluded directory list for server ID {server_id}"
)
def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
dir_list = self.get_excluded_backup_dirs()
@ -381,14 +428,19 @@ class helpers_management:
excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else:
logger.debug(f"Not removing {dir_to_del} from excluded directories - not in the excluded directory list for server ID {server_id}")
logger.debug(
f"Not removing {dir_to_del} from excluded directories - "
f"not in the excluded directory list for server ID {server_id}"
)
@staticmethod
def clear_unexecuted_commands():
Commands.update({
Commands.executed: True
#pylint: disable=singleton-comparison
}).where(Commands.executed == False).execute()
Commands.update(
{
Commands.executed: True
# pylint: disable=singleton-comparison
}
).where(Commands.executed == False).execute()
management_helper = helpers_management()

View File

@ -4,22 +4,29 @@ import datetime
from app.classes.shared.helpers import helper
try:
from peewee import SqliteDatabase, Model, CharField, DoesNotExist, AutoField, DateTimeField
from peewee import (
SqliteDatabase,
Model,
CharField,
DoesNotExist,
AutoField,
DateTimeField,
)
from playhouse.shortcuts import model_to_dict
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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)
@ -30,9 +37,10 @@ class Roles(Model):
table_name = "roles"
database = database
#************************************************************************************************
# **********************************************************************************
# Roles Helpers
#************************************************************************************************
# **********************************************************************************
class helper_roles:
@staticmethod
def get_all_roles():
@ -52,10 +60,12 @@ class helper_roles:
@staticmethod
def add_role(role_name):
role_id = Roles.insert({
Roles.role_name: role_name.lower(),
Roles.created: helper.get_time_as_string()
}).execute()
role_id = Roles.insert(
{
Roles.role_name: role_name.lower(),
Roles.created: helper.get_time_as_string(),
}
).execute()
return role_id
@staticmethod
@ -74,4 +84,5 @@ class helper_roles:
return False
return True
roles_helper = helper_roles()

View File

@ -7,35 +7,43 @@ from app.classes.shared.helpers import helper
from app.classes.shared.permission_helper import permission_helper
try:
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, CompositeKey, JOIN
from peewee import (
SqliteDatabase,
Model,
ForeignKeyField,
CharField,
CompositeKey,
JOIN,
)
from enum import Enum
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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')
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')
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
@ -46,11 +54,13 @@ class Enum_Permissions_Server(Enum):
Config = 6
Players = 7
class Permissions_Servers:
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)
return Role_Servers.get_or_create(
role_id=role_id, server_id=server, permissions=permissions_mask
)
@staticmethod
def get_permissions_list():
@ -69,13 +79,15 @@ class Permissions_Servers:
@staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server):
return permission_mask[permission_tested.value] == '1'
return permission_mask[permission_tested.value] == "1"
@staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
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)
permission_mask = "".join(list_perms)
return permission_mask
@staticmethod
@ -86,50 +98,71 @@ class Permissions_Servers:
def get_token_permissions(permissions_mask, api_permissions_mask):
permissions_list = []
for member in Enum_Permissions_Server.__members__.items():
if permission_helper.both_have_perm(permissions_mask, api_permissions_mask, member[1]):
if permission_helper.both_have_perm(
permissions_mask, api_permissions_mask, member[1]
):
permissions_list.append(member[1])
return permissions_list
#************************************************************************************************
# **********************************************************************************
# 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)
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)
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()
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).get()
permissions_mask = ""
role_server = (
Role_Servers.select()
.where(Role_Servers.role_id == role_id)
.where(Role_Servers.server_id == server_id)
.get()
)
permissions_mask = role_server.permissions
return permissions_mask
@staticmethod
def get_server_roles(server_id):
role_list = []
roles = Role_Servers.select().where(Role_Servers.server_id == server_id).execute()
roles = (
Role_Servers.select().where(Role_Servers.server_id == server_id).execute()
)
for role in roles:
role_list.append(role.role_id)
return role_list
@staticmethod
def get_role_permissions_list(role_id):
permissions_mask = '00000000'
permissions_mask = "00000000"
role_server = Role_Servers.get_or_none(Role_Servers.role_id == role_id)
if role_server is not None:
permissions_mask = role_server.permissions
@ -138,7 +171,12 @@ class Permissions_Servers:
@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 = (
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)
@ -146,12 +184,21 @@ class Permissions_Servers:
def delete_roles_permissions(role_id, removed_servers=None):
if removed_servers is None:
removed_servers = {}
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
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()
return (
Role_Servers.delete()
.where(Role_Servers.server_id == server_id)
.execute()
)
@staticmethod
def get_user_id_permissions_mask(user_id, server_id: str):
@ -161,14 +208,19 @@ class Permissions_Servers:
@staticmethod
def get_user_permissions_mask(user: Users, server_id: str):
if user.superuser:
permissions_mask = '1' * len(server_permissions.get_permissions_list())
permissions_mask = "1" * len(server_permissions.get_permissions_list())
else:
roles_list = users_helper.get_user_roles_id(user.user_id)
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
role_server = (
Role_Servers.select()
.where(Role_Servers.role_id.in_(roles_list))
.where(Role_Servers.server_id == server_id)
.execute()
)
try:
permissions_mask = role_server[0].permissions
except IndexError:
permissions_mask = '0' * len(server_permissions.get_permissions_list())
permissions_mask = "0" * len(server_permissions.get_permissions_list())
return permissions_mask
@staticmethod
@ -197,7 +249,9 @@ class Permissions_Servers:
if user.superuser:
permissions_list = server_permissions.get_permissions_list()
else:
permissions_mask = server_permissions.get_user_permissions_mask(user, server_id)
permissions_mask = server_permissions.get_user_permissions_mask(
user, server_id
)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list
@ -212,11 +266,18 @@ class Permissions_Servers:
if user.superuser and key.superuser:
return server_permissions.get_permissions_list()
else:
roles_list = users_helper.get_user_roles_id(user['user_id'])
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
roles_list = users_helper.get_user_roles_id(user["user_id"])
role_server = (
Role_Servers.select()
.where(Role_Servers.role_id.in_(roles_list))
.where(Role_Servers.server_id == server_id)
.execute()
)
user_permissions_mask = role_server[0].permissions
key_permissions_mask = key.server_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_mask = permission_helper.combine_masks(
user_permissions_mask, key_permissions_mask
)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list

View File

@ -5,21 +5,31 @@ from app.classes.shared.helpers import helper
from app.classes.shared.main_models import db_helper
try:
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, IntegerField, FloatField
from peewee import (
SqliteDatabase,
Model,
ForeignKeyField,
CharField,
AutoField,
DateTimeField,
BooleanField,
IntegerField,
FloatField,
)
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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)
@ -45,13 +55,13 @@ class Servers(Model):
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)
server_id = ForeignKeyField(Servers, backref="server", index=True)
started = CharField(default="")
running = BooleanField(default=False)
cpu = FloatField(default=0)
@ -72,20 +82,19 @@ class Server_Stats(Model):
crashed = BooleanField(default=False)
downloading = 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,
@ -97,23 +106,25 @@ class helper_servers:
server_log_file: str,
server_stop: str,
server_type: str,
server_port=25565):
return Servers.insert({
Servers.server_name: name,
Servers.server_uuid: server_uuid,
Servers.path: server_dir,
Servers.executable: server_file,
Servers.execution_command: server_command,
Servers.auto_start: False,
Servers.auto_start_delay: 10,
Servers.crash_detection: False,
Servers.log_path: server_log_file,
Servers.server_port: server_port,
Servers.stop_command: server_stop,
Servers.backup_path: backup_path,
Servers.type: server_type
}).execute()
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,
Servers.type: server_type,
}
).execute()
@staticmethod
def get_server_obj(server_id):
@ -141,9 +152,9 @@ class helper_servers:
except IndexError:
return {}
#************************************************************************************************
# **********************************************************************************
# Servers Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def get_all_defined_servers():
query = Servers.select()
@ -155,28 +166,54 @@ class helper_servers:
server_data = []
try:
for s in servers:
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":True})
latest = (
Server_Stats.select()
.where(Server_Stats.server_id == s.get("server_id"))
.order_by(Server_Stats.created.desc())
.limit(1)
)
server_data.append(
{
"server_data": s,
"stats": db_helper.return_rows(latest)[0],
"user_command_permission": True,
}
)
except IndexError as ex:
logger.error(f"Stats collection failed with error: {ex}. Was a server just created?")
logger.error(
f"Stats collection failed with error: {ex}. Was a server just created?"
)
return server_data
@staticmethod
def get_server_friendly_name(server_id):
server_data = servers_helper.get_server_data_by_id(server_id)
friendly_name = f"{server_data.get('server_name', None)} with ID: {server_data.get('server_id', 0)}"
friendly_name = (
f"{server_data.get('server_name', None)} "
f"with ID: {server_data.get('server_id', 0)}"
)
return friendly_name
#************************************************************************************************
# **********************************************************************************
# 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)
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)
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
@ -188,33 +225,42 @@ class helper_servers:
@staticmethod
def sever_crashed(server_id):
with database.atomic():
Server_Stats.update(crashed=True).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(crashed=True).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def set_download(server_id):
with database.atomic():
Server_Stats.update(downloading=True).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(downloading=True).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def finish_download(server_id):
with database.atomic():
Server_Stats.update(downloading=False).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(downloading=False).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def get_download_status(server_id):
download_status = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
download_status = (
Server_Stats.select().where(Server_Stats.server_id == server_id).get()
)
return download_status.downloading
@staticmethod
def server_crash_reset(server_id):
with database.atomic():
Server_Stats.update(crashed=False).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(crashed=False).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def is_crashed(server_id):
svr = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
#pylint: disable=singleton-comparison
# pylint: disable=singleton-comparison
if svr.crashed == True:
return True
else:
@ -223,44 +269,58 @@ class helper_servers:
@staticmethod
def set_update(server_id, value):
try:
#Checks if server even exists
# Checks if server even exists
Server_Stats.select().where(Server_Stats.server_id == server_id)
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
with database.atomic():
Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(updating=value).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def get_update_status(server_id):
update_status = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
update_status = (
Server_Stats.select().where(Server_Stats.server_id == server_id).get()
)
return update_status.updating
@staticmethod
def set_first_run(server_id):
#Sets first run to false
# Sets first run to false
try:
#Checks if server even exists
# Checks if server even exists
Server_Stats.select().where(Server_Stats.server_id == server_id)
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
return
with database.atomic():
Server_Stats.update(first_run=False).where(Server_Stats.server_id == server_id).execute()
Server_Stats.update(first_run=False).where(
Server_Stats.server_id == server_id
).execute()
@staticmethod
def get_first_run(server_id):
first_run = Server_Stats.select().where(Server_Stats.server_id == server_id).get()
first_run = (
Server_Stats.select().where(Server_Stats.server_id == server_id).get()
)
return first_run.first_run
@staticmethod
def get_TTL_without_player(server_id):
last_stat = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).first()
last_stat_with_player = (Server_Stats
.select()
.where(Server_Stats.server_id == server_id)
.where(Server_Stats.online > 0)
.order_by(Server_Stats.created.desc())
.first())
last_stat = (
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
@ -279,11 +339,15 @@ class helper_servers:
except Exception as ex:
logger.error(f"Database entry not found! {ex}")
with database.atomic():
Server_Stats.update(waiting_start=value).where(Server_Stats.server_id == server_id).execute()
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()
waiting_start = (
Server_Stats.select().where(Server_Stats.server_id == server_id).get()
)
return waiting_start.waiting_start

View File

@ -6,22 +6,33 @@ from app.classes.models.roles import Roles, roles_helper
from app.classes.shared.helpers import helper
try:
from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, CompositeKey, DoesNotExist, JOIN
from peewee import (
SqliteDatabase,
Model,
ForeignKeyField,
CharField,
AutoField,
DateTimeField,
BooleanField,
CompositeKey,
DoesNotExist,
JOIN,
)
from playhouse.shortcuts import model_to_dict
except ModuleNotFoundError as e:
helper.auto_installer_fix(e)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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)
@ -34,7 +45,7 @@ class Users(Model):
enabled = BooleanField(default=True)
superuser = BooleanField(default=False)
lang = CharField(default="en_EN")
support_logs = CharField(default = '')
support_logs = CharField(default="")
valid_tokens_from = DateTimeField(default=datetime.datetime.now)
server_order = CharField(default="")
preparing = BooleanField(default=False)
@ -44,40 +55,40 @@ class Users(Model):
database = database
# ************************************************************************************************
# **********************************************************************************
# API Keys Class
# ************************************************************************************************
# **********************************************************************************
class ApiKeys(Model):
token_id = AutoField()
name = CharField(default='', unique=True, index=True)
name = CharField(default="", unique=True, index=True)
created = DateTimeField(default=datetime.datetime.now)
user_id = ForeignKeyField(Users, backref='api_token', index=True)
server_permissions = CharField(default='00000000')
crafty_permissions = CharField(default='000')
user_id = ForeignKeyField(Users, backref="api_token", index=True)
server_permissions = CharField(default="00000000")
crafty_permissions = CharField(default="000")
superuser = BooleanField(default=False)
class Meta:
table_name = 'api_keys'
table_name = "api_keys"
database = database
#************************************************************************************************
# **********************************************************************************
# User Roles Class
#************************************************************************************************
# **********************************************************************************
class User_Roles(Model):
user_id = ForeignKeyField(Users, backref='user_role')
role_id = ForeignKeyField(Roles, backref='user_role')
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')
table_name = "user_roles"
primary_key = CompositeKey("user_id", "role_id")
database = database
#************************************************************************************************
# Users Helpers
#************************************************************************************************
class helper_users:
# **********************************************************************************
# Users Helpers
# **********************************************************************************
class helper_users:
@staticmethod
def get_by_id(user_id):
return Users.get_by_id(user_id)
@ -107,19 +118,19 @@ class helper_users:
def get_user(user_id):
if user_id == 0:
return {
'user_id': 0,
'created': '10/24/2019, 11:34:00',
'last_login': '10/24/2019, 11:34:00',
'last_update': '10/24/2019, 11:34:00',
'last_ip': "127.27.23.89",
'username': "SYSTEM",
'password': None,
'email': "default@example.com",
'enabled': True,
'superuser': True,
'roles': [],
'servers': [],
'support_logs': '',
"user_id": 0,
"created": "10/24/2019, 11:34:00",
"last_login": "10/24/2019, 11:34:00",
"last_update": "10/24/2019, 11:34:00",
"last_ip": "127.27.23.89",
"username": "SYSTEM",
"password": None,
"email": "default@example.com",
"enabled": True,
"superuser": True,
"roles": [],
"servers": [],
"support_logs": "",
}
user = model_to_dict(Users.get(Users.user_id == user_id))
@ -128,7 +139,7 @@ class helper_users:
user = users_helper.add_user_roles(user)
return user
else:
#logger.debug("user: ({}) {}".format(user_id, {}))
# logger.debug("user: ({}) {}".format(user_id, {}))
return {}
@staticmethod
@ -147,31 +158,47 @@ class helper_users:
return user
@staticmethod
def add_user(username: str, password: str = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str:
def add_user(
username: str,
password: str = None,
email: Optional[str] = None,
enabled: bool = True,
superuser: bool = False,
) -> str:
if password is not None:
pw_enc = helper.encode_pass(password)
else:
pw_enc = None
user_id = Users.insert({
Users.username: username.lower(),
Users.password: pw_enc,
Users.email: email,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string()
}).execute()
user_id = Users.insert(
{
Users.username: username.lower(),
Users.password: pw_enc,
Users.email: email,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string(),
}
).execute()
return user_id
@staticmethod
def add_rawpass_user(username: str, password: str = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str:
user_id = Users.insert({
Users.username: username.lower(),
Users.password: password,
Users.email: email,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string()
}).execute()
def add_rawpass_user(
username: str,
password: str = None,
email: Optional[str] = None,
enabled: bool = True,
superuser: bool = False,
) -> str:
user_id = Users.insert(
{
Users.username: username.lower(),
Users.password: password,
Users.email: email,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string(),
}
).execute()
return user_id
@staticmethod
@ -183,7 +210,9 @@ class helper_users:
@staticmethod
def update_server_order(user_id, user_server_order):
Users.update(server_order = user_server_order).where(Users.user_id == user_id).execute()
Users.update(server_order=user_server_order).where(
Users.user_id == user_id
).execute()
@staticmethod
def get_server_order(user_id):
@ -208,20 +237,22 @@ class helper_users:
@staticmethod
def set_support_path(user_id, support_path):
Users.update(support_logs = support_path).where(Users.user_id == user_id).execute()
Users.update(support_logs=support_path).where(
Users.user_id == user_id
).execute()
@staticmethod
def set_prepare(user_id):
Users.update(preparing = True).where(Users.user_id == user_id).execute()
Users.update(preparing=True).where(Users.user_id == user_id).execute()
@staticmethod
def stop_prepare(user_id):
Users.update(preparing = False).where(Users.user_id == user_id).execute()
Users.update(preparing=False).where(Users.user_id == user_id).execute()
@staticmethod
def clear_support_status():
#pylint: disable=singleton-comparison
Users.update(preparing = False).where(Users.preparing == True).execute()
# pylint: disable=singleton-comparison
Users.update(preparing=False).where(Users.preparing == True).execute()
@staticmethod
def user_id_exists(user_id):
@ -229,9 +260,9 @@ class helper_users:
return False
return True
#************************************************************************************************
# User_Roles Methods
#************************************************************************************************
# **********************************************************************************
# User_Roles Methods
# **********************************************************************************
@staticmethod
def get_or_create(user_id, role_id):
@ -242,7 +273,7 @@ class helper_users:
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'])
roles_list.append(roles_helper.get_role(r.role_id)["role_id"])
return roles_list
@staticmethod
@ -250,37 +281,41 @@ class helper_users:
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'])
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()
User_Roles.insert(
{User_Roles.user_id: user_id, User_Roles.role_id: role_id}
).execute()
@staticmethod
def add_user_roles(user: Union[dict, Users]):
if isinstance(user, dict):
user_id = user['user_id']
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
# I just copied this code from get_user,
# it had those TODOs & comments made by mac - Lukas
roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id)
roles_query = (
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)
if isinstance(user, dict):
user['roles'] = roles
user["roles"] = roles
else:
user.roles = roles
#logger.debug("user: ({}) {}".format(user_id, user))
# logger.debug("user: ({}) {}".format(user_id, user))
return user
@staticmethod
@ -293,15 +328,17 @@ class helper_users:
@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()
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()
# ************************************************************************************************
# ApiKeys Methods
# ************************************************************************************************
# **********************************************************************************
# ApiKeys Methods
# **********************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
@ -313,18 +350,29 @@ class helper_users:
@staticmethod
def add_user_api_key(
name: str,
user_id: str,
superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None):
return ApiKeys.insert({
ApiKeys.name: name,
ApiKeys.user_id: user_id,
**({ApiKeys.server_permissions: server_permissions_mask} if server_permissions_mask is not None else {}),
**({ApiKeys.crafty_permissions: crafty_permissions_mask} if crafty_permissions_mask is not None else {}),
ApiKeys.superuser: superuser
}).execute()
name: str,
user_id: str,
superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None,
):
return ApiKeys.insert(
{
ApiKeys.name: name,
ApiKeys.user_id: user_id,
**(
{ApiKeys.server_permissions: server_permissions_mask}
if server_permissions_mask is not None
else {}
),
**(
{ApiKeys.crafty_permissions: crafty_permissions_mask}
if crafty_permissions_mask is not None
else {}
),
ApiKeys.superuser: superuser,
}
).execute()
@staticmethod
def delete_user_api_keys(user_id: str):
@ -335,5 +383,4 @@ class helper_users:
ApiKeys.delete().where(ApiKeys.token_id == key_id).execute()
users_helper = helper_users()

View File

@ -14,12 +14,13 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
class Authentication:
def __init__(self):
self.secret = "my secret"
self.secret = helper.get_setting('apikey_secret', None)
self.secret = helper.get_setting("apikey_secret", None)
if self.secret is None or self.secret == 'random':
if self.secret is None or self.secret == "random":
self.secret = helper.random_string_generator(64)
@staticmethod
@ -27,13 +28,9 @@ class Authentication:
if extra is None:
extra = {}
return jwt.encode(
{
'user_id': user_id,
'iat': int(time.time()),
**extra
},
{"user_id": user_id, "iat": int(time.time()), **extra},
authentication.secret,
algorithm="HS256"
algorithm="HS256",
)
@staticmethod
@ -49,23 +46,26 @@ class Authentication:
return None
@staticmethod
def check(token) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
def check(
token,
) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
try:
data = jwt.decode(token, authentication.secret, algorithms=["HS256"])
except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error)
return None
iat: int = data['iat']
iat: int = data["iat"]
key: Optional[ApiKeys] = None
if 'token_id' in data:
key_id = data['token_id']
if "token_id" in data:
key_id = data["token_id"]
key = users_helper.get_user_api_key(key_id)
if key is None:
return None
user_id: str = data['user_id']
user_id: str = data["user_id"]
user = users_helper.get_user(user_id)
# TODO: Have a cache or something so we don't constantly have to query the database
if int(user.get('valid_tokens_from').timestamp()) < iat:
# TODO: Have a cache or something so we don't constantly
# have to query the database
if int(user.get("valid_tokens_from").timestamp()) < iat:
# Success!
return key, data, user
else:

View File

@ -11,8 +11,8 @@ from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__)
class MainPrompt(cmd.Cmd):
class MainPrompt(cmd.Cmd):
def __init__(self, tasks_manager, migration_manager):
super().__init__()
self.tasks_manager = tasks_manager
@ -25,48 +25,53 @@ class MainPrompt(cmd.Cmd):
def emptyline():
pass
#pylint: disable=unused-argument
# pylint: disable=unused-argument
def do_exit(self, line):
self.tasks_manager._main_graceful_exit()
self.universal_exit()
def do_migrations(self, line):
if line == 'up':
if line == "up":
self.migration_manager.up()
elif line == 'down':
elif line == "down":
self.migration_manager.down()
elif line == 'done':
elif line == "done":
console.info(self.migration_manager.done)
elif line == 'todo':
elif line == "todo":
console.info(self.migration_manager.todo)
elif line == 'diff':
elif line == "diff":
console.info(self.migration_manager.diff)
elif line == 'info':
console.info(f'Done: {self.migration_manager.done}')
console.info(f'FS: {self.migration_manager.todo}')
console.info(f'Todo: {self.migration_manager.diff}')
elif line.startswith('add '):
migration_name = line[len('add '):]
elif line == "info":
console.info(f"Done: {self.migration_manager.done}")
console.info(f"FS: {self.migration_manager.todo}")
console.info(f"Todo: {self.migration_manager.diff}")
elif line.startswith("add "):
migration_name = line[len("add ") :]
self.migration_manager.create(migration_name, False)
else:
console.info('Unknown migration command')
console.info("Unknown migration command")
@staticmethod
def do_threads(_line):
for thread in threading.enumerate():
if sys.version_info >= (3, 8):
print(f'Name: {thread.name} Identifier: {thread.ident} TID/PID: {thread.native_id}')
print(
f"Name: {thread.name} Identifier: "
f"{thread.ident} TID/PID: {thread.native_id}"
)
else:
print(f'Name: {thread.name} Identifier: {thread.ident}')
print(f"Name: {thread.name} Identifier: {thread.ident}")
def do_import3(self, _line):
import3.start_import()
def universal_exit(self):
logger.info("Stopping all server daemons / threads")
console.info("Stopping all server daemons / threads - This may take a few seconds")
console.info(
"Stopping all server daemons / threads - This may take a few seconds"
)
websocket_helper.disconnect_all()
console.info('Waiting for main thread to stop')
console.info("Waiting for main thread to stop")
while True:
if self.tasks_manager.get_main_thread_run_status():
sys.exit(0)

View File

@ -12,16 +12,18 @@ except ModuleNotFoundError as ex:
logger.critical(f"Import Error: Unable to load {ex.name} module", exc_info=True)
print(f"Import Error: Unable to load {ex.name} module")
from app.classes.shared.installer import installer
installer.do_install()
class Console:
installer.do_install()
class Console:
def __init__(self):
if 'colorama' in sys.modules:
if "colorama" in sys.modules:
init()
@staticmethod
def do_print(message, color):
if 'termcolor' in sys.modules or 'colorama' in sys.modules:
if "termcolor" in sys.modules or "colorama" in sys.modules:
print(colored(message, color))
else:
print(message)

View File

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

View File

@ -6,12 +6,9 @@ from zipfile import ZipFile, ZIP_DEFLATED
logger = logging.getLogger(__name__)
class FileHelpers:
allowed_quotes = [
"\"",
"'",
"`"
]
allowed_quotes = ['"', "'", "`"]
def del_dirs(self, path):
path = pathlib.Path(path)
@ -32,7 +29,7 @@ class FileHelpers:
path = pathlib.Path(path)
try:
logger.debug(f"Deleting file: {path}")
#Remove the file
# Remove the file
os.remove(path)
return True
except FileNotFoundError:
@ -59,43 +56,60 @@ class FileHelpers:
@staticmethod
def make_archive(path_to_destination, path_to_zip):
# create a ZipFile object
path_to_destination += '.zip'
with ZipFile(path_to_destination, 'w') as z:
path_to_destination += ".zip"
with ZipFile(path_to_destination, "w") as z:
for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip
for file in files:
try:
logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt":
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file))
z.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else:
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file))
z.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e:
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}")
logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
return True
@staticmethod
def make_compressed_archive(path_to_destination, path_to_zip):
# create a ZipFile object
path_to_destination += '.zip'
with ZipFile(path_to_destination, 'w', ZIP_DEFLATED) as z:
path_to_destination += ".zip"
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as z:
for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip
for file in files:
try:
logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt":
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, ""), file))
z.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else:
z.write(os.path.join(root, file), os.path.join(root.replace(ziproot, "/"), file))
z.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e:
logger.warning(f"Error backing up: {os.path.join(root, file)}! - Error was: {e}")
logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
return True
file_helper = FileHelpers()

View File

@ -37,28 +37,27 @@ except ModuleNotFoundError as err:
print(f"Import Error: Unable to load {err.name} module")
installer.do_install()
class Helpers:
allowed_quotes = [
"\"",
"'",
"`"
]
allowed_quotes = ['"', "'", "`"]
def __init__(self):
self.root_dir = os.path.abspath(os.path.curdir)
self.config_dir = os.path.join(self.root_dir, 'app', 'config')
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.config_dir = os.path.join(self.root_dir, "app", "config")
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')
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")
self.ensure_dir_exists(os.path.join(self.root_dir, 'app', 'config', 'db'))
self.db_path = os.path.join(self.root_dir, 'app', 'config', 'db', 'crafty.sqlite')
self.serverjar_cache = os.path.join(self.config_dir, 'serverjars.json')
self.credits_cache = os.path.join(self.config_dir, 'credits.json')
self.ensure_dir_exists(os.path.join(self.root_dir, "app", "config", "db"))
self.db_path = os.path.join(
self.root_dir, "app", "config", "db", "crafty.sqlite"
)
self.serverjar_cache = os.path.join(self.config_dir, "serverjars.json")
self.credits_cache = os.path.join(self.config_dir, "credits.json")
self.passhasher = PasswordHasher()
self.exiting = False
@ -74,7 +73,7 @@ class Helpers:
def check_file_perms(self, path):
try:
open(path, "r", encoding='utf-8').close()
open(path, "r", encoding="utf-8").close()
logger.info(f"{path} is readable")
return True
except PermissionError:
@ -97,7 +96,7 @@ class Helpers:
@staticmethod
def check_internet():
try:
requests.get('https://google.com', timeout=1)
requests.get("https://google.com", timeout=1)
return True
except Exception:
return False
@ -105,9 +104,9 @@ class Helpers:
@staticmethod
def check_port(server_port):
try:
ip = get('https://api.ipify.org').content.decode('utf8')
ip = get("https://api.ipify.org").content.decode("utf8")
except:
ip = 'google.com'
ip = "google.com"
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
a_socket.settimeout(20.0)
@ -125,7 +124,7 @@ class Helpers:
def check_server_conn(server_port):
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
a_socket.settimeout(10.0)
ip = '127.0.0.1'
ip = "127.0.0.1"
location = (ip, server_port)
result_of_check = a_socket.connect_ex(location)
@ -139,43 +138,54 @@ class Helpers:
@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
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: # if set, begin a new argument and increment the command index. Continue the loop.
if c == ' ':
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: # 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 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] += "\\"
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
if c == "\\": # if the current character is an escape character,
# set the esc flag and continue to next loop
esc = True
elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote,
# set the new argument flag and continue to next loop
elif (
c == " " and stch is None
): # if we encounter a space and are not dealing with a quote,
# set the new argument flag and continue to next loop
np = True
elif c == stch: # if we encounter the character that matches our start quote, end the quote and continue to next loop
elif (
c == stch
): # if we encounter the character that matches our start quote,
# end the quote and continue to next loop
stch = None
elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character,
# start a quote and proceed to the next loop
elif stch is None and (
c in Helpers.allowed_quotes
): # if we're not in the middle of a quote and we get a quotable
# character, start a quote and proceed to the next loop
stch = c
else: # else, just store the character in the current arg
else: # else, just store the character in the current arg
cmd_out[ci] += c
return cmd_out
def get_setting(self, key, default_return=False):
try:
with open(self.settings_file, "r", encoding='utf-8') as f:
with open(self.settings_file, "r", encoding="utf-8") as f:
data = json.load(f)
if key in data.keys():
@ -187,8 +197,12 @@ class Helpers:
return default_return
except Exception as e:
logger.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}")
console.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}")
logger.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
console.critical(
f"Config File Error: Unable to read {self.settings_file} due to {e}"
)
return default_return
@ -196,10 +210,10 @@ class Helpers:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
s.connect(("10.255.255.255", 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
IP = "127.0.0.1"
finally:
s.close()
return IP
@ -207,7 +221,9 @@ class Helpers:
def get_version(self):
version_data = {}
try:
with open(os.path.join(self.config_dir, 'version.json'), 'r', encoding='utf-8') as f:
with open(
os.path.join(self.config_dir, "version.json"), "r", encoding="utf-8"
) as f:
version_data = json.load(f)
except Exception as e:
@ -217,9 +233,12 @@ class Helpers:
@staticmethod
def get_announcements():
r = requests.get('https://craftycontrol.com/notify.json', timeout=2)
data = '[{"id":"1","date":"Unknown","title":"Error getting Announcements","desc":"Error getting ' \
'Announcements","link":""}] '
r = requests.get("https://craftycontrol.com/notify.json", timeout=2)
data = (
'[{"id":"1","date":"Unknown",'
'"title":"Error getting Announcements",'
'"desc":"Error getting Announcements","link":""}]'
)
if r.status_code in [200, 201]:
try:
@ -229,14 +248,13 @@ class Helpers:
return data
def get_version_string(self):
version_data = self.get_version()
major = version_data.get('major', '?')
minor = version_data.get('minor', '?')
sub = version_data.get('sub', '?')
meta = version_data.get('meta', '?')
major = version_data.get("major", "?")
minor = version_data.get("minor", "?")
sub = version_data.get("sub", "?")
meta = version_data.get("meta", "?")
# set some defaults if we don't get version_data from our helper
version = f"{major}.{minor}.{sub}-{meta}"
@ -256,25 +274,31 @@ class Helpers:
# our regex replacements
# note these are in a tuple
user_keywords = self.get_setting('keywords')
user_keywords = self.get_setting("keywords")
replacements = [
(r'(\[.+?/INFO\])', r'<span class="mc-log-info">\1</span>'),
(r'(\[.+?/WARN\])', r'<span class="mc-log-warn">\1</span>'),
(r'(\[.+?/ERROR\])', r'<span class="mc-log-error">\1</span>'),
(r'(\[.+?/FATAL\])', r'<span class="mc-log-fatal">\1</span>'),
(r'(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])', r'<span class="mc-log-keyword">\1</span>'),
(r'\[(\d\d:\d\d:\d\d)\]', r'<span class="mc-log-time">[\1]</span>'),
(r'(\[.+? INFO\])', r'<span class="mc-log-info">\1</span>'),
(r'(\[.+? WARN\])', r'<span class="mc-log-warn">\1</span>'),
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>'),
(r'(\[.+? FATAL\])', r'<span class="mc-log-fatal">\1</span>')
(r"(\[.+?/INFO\])", r'<span class="mc-log-info">\1</span>'),
(r"(\[.+?/WARN\])", r'<span class="mc-log-warn">\1</span>'),
(r"(\[.+?/ERROR\])", r'<span class="mc-log-error">\1</span>'),
(r"(\[.+?/FATAL\])", r'<span class="mc-log-fatal">\1</span>'),
(
r"(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])",
r'<span class="mc-log-keyword">\1</span>',
),
(r"\[(\d\d:\d\d:\d\d)\]", r'<span class="mc-log-time">[\1]</span>'),
(r"(\[.+? INFO\])", r'<span class="mc-log-info">\1</span>'),
(r"(\[.+? WARN\])", r'<span class="mc-log-warn">\1</span>'),
(r"(\[.+? ERROR\])", r'<span class="mc-log-error">\1</span>'),
(r"(\[.+? FATAL\])", r'<span class="mc-log-fatal">\1</span>'),
]
# highlight users keywords
for keyword in user_keywords:
# pylint: disable=consider-using-f-string
search_replace = (r'({})'.format(keyword), r'<span class="mc-log-keyword">\1</span>')
search_replace = (
r"({})".format(keyword),
r'<span class="mc-log-keyword">\1</span>',
)
replacements.append(search_replace)
for old, new in replacements:
@ -282,9 +306,8 @@ class Helpers:
return line
def validate_traversal(self, base_path, filename):
logger.debug(f"Validating traversal (\"{base_path}\", \"{filename}\")")
logger.debug(f'Validating traversal ("{base_path}", "{filename}")')
base = pathlib.Path(base_path).resolve()
file = pathlib.Path(filename)
fileabs = base.joinpath(file).resolve()
@ -294,7 +317,6 @@ class Helpers:
else:
raise ValueError("Path traversal detected")
def tail_file(self, file_name, number_lines=20):
if not self.check_file_exists(file_name):
logger.warning(f"Unable to find file to tail: {file_name}")
@ -307,7 +329,7 @@ class Helpers:
line_buffer = number_lines * avg_line_length
# open our file
with open(file_name, 'r', encoding='utf-8') as f:
with open(file_name, "r", encoding="utf-8") as f:
# seek
f.seek(0, 2)
@ -315,15 +337,18 @@ class Helpers:
# get file size
fsize = f.tell()
# set pos @ last n chars (buffer from above = number of lines * avg_line_length)
f.seek(max(fsize-line_buffer, 0), 0)
# set pos @ last n chars
# (buffer from above = number of lines * avg_line_length)
f.seek(max(fsize - line_buffer, 0), 0)
# read file til the end
try:
lines = f.readlines()
except Exception as e:
logger.warning(f'Unable to read a line in the file:{file_name} - due to error: {e}')
logger.warning(
f"Unable to read a line in the file:{file_name} - due to error: {e}"
)
# now we are done getting the lines, let's return it
return lines
@ -332,7 +357,7 @@ class Helpers:
def check_writeable(path: str):
filename = os.path.join(path, "tempfile.txt")
try:
open(filename, "w", encoding='utf-8').close()
open(filename, "w", encoding="utf-8").close()
os.remove(filename)
logger.info(f"{filename} is writable")
@ -355,31 +380,36 @@ class Helpers:
return False
def unzipFile(self, zip_path):
new_dir_list = zip_path.split('/')
new_dir = ''
for i in range(len(new_dir_list)-1):
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]
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:
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(tempDir)
for i in enumerate(zip_ref.filelist):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
i
].filename.endswith("/"):
break
full_root_path = tempDir
for item in os.listdir(full_root_path):
try:
file_helper.move_dir(os.path.join(full_root_path, item), os.path.join(new_dir, item))
file_helper.move_dir(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
except Exception as ex:
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
except Exception as ex:
print(ex)
else:
@ -387,8 +417,8 @@ class Helpers:
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')
log_file = os.path.join(os.path.curdir, "logs", "commander.log")
session_log_file = os.path.join(os.path.curdir, "logs", "session.log")
logger.info("Checking app directory writable")
@ -402,13 +432,13 @@ class Helpers:
# ensure the log directory is there
try:
with suppress(FileExistsError):
os.makedirs(os.path.join(self.root_dir, 'logs'))
os.makedirs(os.path.join(self.root_dir, "logs"))
except Exception as e:
console.error(f"Failed to make logs directory with error: {e} ")
# ensure the log file is there
try:
open(log_file, 'a', encoding='utf-8').close()
open(log_file, "a", encoding="utf-8").close()
except Exception as e:
console.critical(f"Unable to open log file! {e}")
sys.exit(1)
@ -426,7 +456,8 @@ class Helpers:
@staticmethod
def calc_percent(source_path, dest_path):
#calculates percentable of zip from drive. Not with compression. For backups and support logs
# calculates percentable of zip from drive. Not with compression.
# (For backups and support logs)
source_size = 0
files_count = 0
for path, _dirs, files in os.walk(source_path):
@ -435,47 +466,41 @@ class Helpers:
source_size += os.stat(fp).st_size
files_count += 1
dest_size = os.path.getsize(str(dest_path))
percent = round((dest_size/source_size) * 100, 1)
percent = round((dest_size / source_size) * 100, 1)
if percent >= 0:
results = {
"percent": percent,
"total_files": files_count
}
results = {"percent": percent, "total_files": files_count}
else:
results = {
"percent": 0,
"total_files": 0
}
results = {"percent": 0, "total_files": 0}
return results
@staticmethod
def check_file_exists(path: str):
logger.debug(f'Looking for path: {path}')
logger.debug(f"Looking for path: {path}")
if os.path.exists(path) and os.path.isfile(path):
logger.debug(f'Found path: {path}')
logger.debug(f"Found path: {path}")
return True
else:
return False
@staticmethod
def human_readable_file_size(num: int, suffix='B'):
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
def human_readable_file_size(num: int, suffix="B"):
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
if abs(num) < 1024.0:
# pylint: disable=consider-using-f-string
# pylint: disable=consider-using-f-string
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
# pylint: disable=consider-using-f-string
return "%.1f%s%s" % (num, 'Y', suffix)
return "%.1f%s%s" % (num, "Y", suffix)
@staticmethod
def check_path_exists(path: str):
if not path:
return False
logger.debug(f'Looking for path: {path}')
logger.debug(f"Looking for path: {path}")
if os.path.exists(path):
logger.debug(f'Found path: {path}')
logger.debug(f"Found path: {path}")
return True
else:
return False
@ -483,12 +508,12 @@ class Helpers:
@staticmethod
def get_file_contents(path: str, lines=100):
contents = ''
contents = ""
if os.path.exists(path) and os.path.isfile(path):
try:
with open(path, 'r', encoding='utf-8') as f:
for line in (f.readlines() [-lines:]):
with open(path, "r", encoding="utf-8") as f:
for line in f.readlines()[-lines:]:
contents = contents + line
return contents
@ -497,7 +522,9 @@ class Helpers:
logger.error(f"Unable to read file: {path}. \n Error: {e}")
return False
else:
logger.error(f"Unable to read file: {path}. File not found, or isn't a file.")
logger.error(
f"Unable to read file: {path}. File not found, or isn't a file."
)
return False
def create_session_file(self, ignore=False):
@ -510,33 +537,39 @@ class Helpers:
file_data = self.get_file_contents(self.session_file)
try:
data = json.loads(file_data)
pid = data.get('pid')
started = data.get('started')
pid = data.get("pid")
started = data.get("started")
if psutil.pid_exists(pid):
console.critical(f"Another Crafty Controller agent seems to be running...\npid: {pid} \nstarted on: {started}")
console.critical(
f"Another Crafty Controller agent seems to be running..."
f"\npid: {pid} \nstarted on: {started}"
)
logger.critical("Found running crafty process. Exiting.")
sys.exit(1)
else:
logger.info("No process found for pid. Assuming crafty crashed. Deleting stale session.lock")
logger.info(
"No process found for pid. Assuming "
"crafty crashed. Deleting stale session.lock"
)
os.remove(self.session_file)
except Exception as e:
logger.error(f"Failed to locate existing session.lock with error: {e} ")
console.error(f"Failed to locate existing session.lock with error: {e} ")
console.error(
f"Failed to locate existing session.lock with error: {e} "
)
sys.exit(1)
pid = os.getpid()
now = datetime.now()
session_data = {
'pid': pid,
'started': now.strftime("%d-%m-%Y, %H:%M:%S")
}
with open(self.session_file, 'w', encoding='utf-8') as f:
session_data = {"pid": pid, "started": now.strftime("%d-%m-%Y, %H:%M:%S")}
with open(self.session_file, "w", encoding="utf-8") as f:
json.dump(session_data, f, indent=True)
# because this is a recursive function, we will return bytes, and set human readable later
# because this is a recursive function, we will return bytes,
# and set human readable later
def get_dir_size(self, path: str):
total = 0
for entry in os.scandir(path):
@ -548,26 +581,30 @@ class Helpers:
@staticmethod
def list_dir_by_date(path: str, reverse=False):
return [str(p) for p in sorted(pathlib.Path(path).iterdir(), key=os.path.getmtime, reverse=reverse)]
return [
str(p)
for p in sorted(
pathlib.Path(path).iterdir(), key=os.path.getmtime, reverse=reverse
)
]
def get_human_readable_files_sizes(self, paths: list):
sizes = []
for p in paths:
sizes.append({
"path": p,
"size": self.human_readable_file_size(os.stat(p).st_size)
})
sizes.append(
{"path": p, "size": self.human_readable_file_size(os.stat(p).st_size)}
)
return sizes
@staticmethod
def base64_encode_string(fun_str: str):
s_bytes = str(fun_str).encode('utf-8')
s_bytes = str(fun_str).encode("utf-8")
b64_bytes = base64.encodebytes(s_bytes)
return b64_bytes.decode('utf-8')
return b64_bytes.decode("utf-8")
@staticmethod
def base64_decode_string(fun_str: str):
s_bytes = str(fun_str).encode('utf-8')
s_bytes = str(fun_str).encode("utf-8")
b64_bytes = base64.decodebytes(s_bytes)
return b64_bytes.decode("utf-8")
@ -578,7 +615,8 @@ class Helpers:
"""
ensures a directory exists
Checks for the existence of a directory, if the directory isn't there, this function creates the directory
Checks for the existence of a directory, if the directory isn't there,
this function creates the directory
Args:
path (string): the path you are checking for
@ -666,23 +704,23 @@ class Helpers:
random_generator() = G8sjO2
random_generator(3, abcdef) = adf
"""
return ''.join(random.choice(chars) for x in range(size))
return "".join(random.choice(chars) for x in range(size))
@staticmethod
def is_os_windows():
if os.name == 'nt':
if os.name == "nt":
return True
else:
return False
@staticmethod
def wtol_path(w_path):
l_path = w_path.replace('\\', '/')
l_path = w_path.replace("\\", "/")
return l_path
@staticmethod
def ltow_path(l_path):
w_path = l_path.replace('/', '\\')
w_path = l_path.replace("/", "\\")
return w_path
@staticmethod
@ -694,10 +732,10 @@ class Helpers:
data = {}
if self.check_file_exists(default_file):
with open(default_file, 'r', encoding='utf-8') as f:
with open(default_file, "r", encoding="utf-8") as f:
data = json.load(f)
del_json = helper.get_setting('delete_default_json')
del_json = helper.get_setting("delete_default_json")
if del_json:
os.remove(default_file)
@ -714,14 +752,15 @@ class Helpers:
dir_list.append(item)
else:
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
)
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if os.path.isdir(rel):
output += \
f"""<li class="tree-item" data-path="{dpath}">
output += f"""<li class="tree-item" data-path="{dpath}">
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i class="far fa-folder"></i>
@ -729,15 +768,15 @@ class Helpers:
{filename}
</span>
</div><li>
\n"""\
\n"""
else:
if filename != "crafty_managed.txt":
output += f"""<li
class="tree-nested d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}"
data-name="{filename}"
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{filename}</li>"""
onclick="clickOnFile(event)"><span style="margin-right: 6px;">
<i class="far fa-file"></i></span>{filename}</li>"""
return output
@staticmethod
@ -750,49 +789,45 @@ class Helpers:
dir_list.append(item)
else:
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(unsorted_files, key=str.casefold)
output += \
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
)
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
for raw_filename in file_list:
filename = html.escape(raw_filename)
dpath = os.path.join(folder, filename)
rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel):
output += \
f"""<li class="tree-item" data-path="{dpath}">
output += f"""<li class="tree-item" data-path="{dpath}">
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{filename}
</span>
</div><li>"""\
</div><li>"""
else:
if filename != "crafty_managed.txt":
output += f"""<li
class="tree-nested d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}"
data-name="{filename}"
onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>{filename}</li>"""
output += '</ul>\n'
onclick="clickOnFile(event)"><span style="margin-right: 6px;">
<i class="far fa-file"></i></span>{filename}</li>"""
output += "</ul>\n"
return output
@staticmethod
def generate_zip_tree(folder, output=""):
file_list = os.listdir(folder)
file_list = sorted(file_list, key=str.casefold)
output += \
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if os.path.isdir(rel):
output += \
f"""<li class="tree-item" data-path="{dpath}">
output += f"""<li class="tree-item" data-path="{dpath}">
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="radio" name="root_path" value="{dpath}">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
@ -801,24 +836,20 @@ class Helpers:
{filename}
</span>
</input></div><li>
\n"""\
\n"""
return output
@staticmethod
def generate_zip_dir(folder, output=""):
file_list = os.listdir(folder)
file_list = sorted(file_list, key=str.casefold)
output += \
f"""<ul class="tree-nested d-block" id="{folder}ul">"""\
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if os.path.isdir(rel):
output += \
f"""<li class="tree-item" data-path="{dpath}">
output += f"""<li class="tree-item" data-path="{dpath}">
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
<input type="radio" name="root_path" value="{dpath}">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
@ -826,35 +857,33 @@ class Helpers:
<i class="far fa-folder-open"></i>
{filename}
</span>
</input></div><li>"""\
</input></div><li>"""
return output
@staticmethod
def unzipServer(zip_path, user_id):
if helper.check_file_perms(zip_path):
tempDir = tempfile.mkdtemp()
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
#extracts archive to temp directory
with zipfile.ZipFile(zip_path, "r") as zip_ref:
# extracts archive to temp directory
zip_ref.extractall(tempDir)
if user_id:
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
'path': tempDir
})
websocket_helper.broadcast_user(
user_id, "send_temp_path", {"path": tempDir}
)
@staticmethod
def backup_select(path, user_id):
if user_id:
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
'path': path
})
websocket_helper.broadcast_user(user_id, "send_temp_path", {"path": path})
@staticmethod
def unzip_backup_archive(backup_path, zip_name):
zip_path = os.path.join(backup_path, zip_name)
if helper.check_file_perms(zip_path):
tempDir = tempfile.mkdtemp()
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
#extracts archive to temp directory
with zipfile.ZipFile(zip_path, "r") as zip_ref:
# extracts archive to temp directory
zip_ref.extractall(tempDir)
return tempDir
else:
@ -862,14 +891,19 @@ class Helpers:
@staticmethod
def in_path(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
# 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])
# 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 in_path_old(x, y):
@ -901,20 +935,20 @@ class Helpers:
return False
return True
@staticmethod
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text[len(prefix) :]
return text
@staticmethod
def getLangPage(text):
lang = text.split("_")[0]
region = text.split("_")[1]
if region == 'EN':
return 'en'
if region == "EN":
return "en"
else:
return lang+"-"+region
return lang + "-" + region
helper = Helpers()

View File

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

View File

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

View File

@ -28,7 +28,8 @@ from app.classes.web.websocket_helper import websocket_helper
try:
from peewee import DoesNotExist
#TZLocal is set as a hidden import on win pipeline
# TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
from apscheduler.schedulers.background import BackgroundScheduler
@ -37,8 +38,8 @@ except ModuleNotFoundError as err:
logger = logging.getLogger(__name__)
class Controller:
class Controller:
def __init__(self):
self.servers_list = []
self.stats = Stats(self)
@ -57,12 +58,15 @@ class Controller:
logger.info(f"Checking to see if we already registered {server_id_to_check}")
for s in self.servers_list:
known_server = s.get('server_id')
known_server = s.get("server_id")
if known_server is None:
return False
if known_server == server_id_to_check:
logger.info(f'skipping initialization of server {server_id_to_check} because it is already loaded')
logger.info(
f"skipping initialization of server {server_id_to_check} "
f"because it is already loaded"
)
return True
return False
@ -72,20 +76,30 @@ class Controller:
servers = self.servers.get_all_defined_servers()
for s in servers:
server_id = s.get('server_id')
server_id = s.get("server_id")
# if we have already initialized this server, let's skip it.
if self.check_server_loaded(server_id):
continue
# if this server path no longer exists - let's warn and bomb out
if not helper.check_path_exists(helper.get_os_understandable_path(s['path'])):
logger.warning(f"Unable to find server {s['server_name']} at path {s['path']}. Skipping this server")
if not helper.check_path_exists(
helper.get_os_understandable_path(s["path"])
):
logger.warning(
f"Unable to find server {s['server_name']} at path {s['path']}. "
f"Skipping this server"
)
console.warning(f"Unable to find server {s['server_name']} at path {s['path']}. Skipping this server")
console.warning(
f"Unable to find server {s['server_name']} at path {s['path']}. "
f"Skipping this server"
)
continue
settings_file = os.path.join(helper.get_os_understandable_path(s['path']), 'server.properties')
settings_file = os.path.join(
helper.get_os_understandable_path(s["path"]), "server.properties"
)
# if the properties file isn't there, let's warn
if not helper.check_file_exists(settings_file):
@ -96,27 +110,29 @@ class Controller:
settings = ServerProps(settings_file)
temp_server_dict = {
'server_id': s.get('server_id'),
'server_data_obj': s,
'server_obj': Server(self.stats),
'server_settings': settings.props
"server_id": s.get("server_id"),
"server_data_obj": s,
"server_obj": Server(self.stats),
"server_settings": settings.props,
}
# setup the server, do the auto start and all that jazz
temp_server_dict['server_obj'].do_server_setup(s)
temp_server_dict["server_obj"].do_server_setup(s)
# 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)
if s["auto_start"]:
self.servers.set_waiting_start(s["server_id"], True)
self.refresh_server_settings(s['server_id'])
self.refresh_server_settings(s["server_id"])
console.info(f"Loaded Server: ID {s['server_id']}" +
f" | Name: {s['server_name']}" +
f" | Autostart: {s['auto_start']}" +
f" | Delay: {s['auto_start_delay']} ")
console.info(
f"Loaded Server: ID {s['server_id']}"
+ f" | Name: {s['server_name']}"
+ f" | Autostart: {s['auto_start']}"
+ f" | Delay: {s['auto_start_delay']} "
)
def refresh_server_settings(self, server_id: int):
server_obj = self.get_server_obj(server_id)
@ -132,80 +148,106 @@ class Controller:
def set_project_root(self, root_dir):
self.project_root = root_dir
def package_support_logs(self, exec_user):
if exec_user['preparing']:
if exec_user["preparing"]:
return
self.users.set_prepare(exec_user['user_id'])
#pausing so on screen notifications can run for user
self.users.set_prepare(exec_user["user_id"])
# pausing so on screen notifications can run for user
time.sleep(7)
websocket_helper.broadcast_user(exec_user['user_id'], 'notification', 'Preparing your support logs')
websocket_helper.broadcast_user(
exec_user["user_id"], "notification", "Preparing your support logs"
)
tempDir = tempfile.mkdtemp()
tempZipStorage = tempfile.mkdtemp()
full_temp = os.path.join(tempDir, 'support_logs')
full_temp = os.path.join(tempDir, "support_logs")
os.mkdir(full_temp)
tempZipStorage = os.path.join(tempZipStorage, "support_logs")
crafty_path = os.path.join(full_temp, "crafty")
os.mkdir(crafty_path)
server_path = os.path.join(full_temp, "server")
os.mkdir(server_path)
if exec_user['superuser']:
if exec_user["superuser"]:
auth_servers = self.servers.get_all_defined_servers()
else:
user_servers = self.servers.get_authorized_servers(int(exec_user['user_id']))
user_servers = self.servers.get_authorized_servers(
int(exec_user["user_id"])
)
auth_servers = []
for server in user_servers:
if Enum_Permissions_Server.Logs in self.server_perms.get_user_id_permissions_list(exec_user['user_id'], server["server_id"]):
if (
Enum_Permissions_Server.Logs
in self.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server["server_id"]
)
):
auth_servers.append(server)
else:
logger.info(f"Logs permission not available for server {server['server_name']}. Skipping.")
#we'll iterate through our list of log paths from auth servers.
logger.info(
f"Logs permission not available for server "
f"{server['server_name']}. Skipping."
)
# we'll iterate through our list of log paths from auth servers.
for server in auth_servers:
final_path = os.path.join(server_path, str(server['server_name']))
final_path = os.path.join(server_path, str(server["server_name"]))
try:
os.mkdir(final_path)
except FileExistsError:
final_path += '_'+server['server_uuid']
final_path += "_" + server["server_uuid"]
os.mkdir(final_path)
try:
file_helper.copy_file(server['log_path'], final_path)
file_helper.copy_file(server["log_path"], final_path)
except Exception as e:
logger.warning(f"Failed to copy file with error: {e}")
#Copy crafty logs to archive dir
full_log_name = os.path.join(crafty_path, 'logs')
file_helper.copy_dir(os.path.join(self.project_root, 'logs'), full_log_name)
self.support_scheduler.add_job(self.log_status, 'interval', seconds=1, id="logs_"+str(exec_user['user_id']), args = [full_temp,
tempZipStorage +'.zip', exec_user])
# Copy crafty logs to archive dir
full_log_name = os.path.join(crafty_path, "logs")
file_helper.copy_dir(os.path.join(self.project_root, "logs"), full_log_name)
self.support_scheduler.add_job(
self.log_status,
"interval",
seconds=1,
id="logs_" + str(exec_user["user_id"]),
args=[full_temp, tempZipStorage + ".zip", exec_user],
)
file_helper.make_archive(tempZipStorage, tempDir)
if len(websocket_helper.clients) > 0:
websocket_helper.broadcast_user(exec_user['user_id'], 'support_status_update', helper.calc_percent(full_temp, tempZipStorage +'.zip'))
websocket_helper.broadcast_user(
exec_user["user_id"],
"support_status_update",
helper.calc_percent(full_temp, tempZipStorage + ".zip"),
)
tempZipStorage += '.zip'
websocket_helper.broadcast_user(exec_user['user_id'], 'send_logs_bootbox', {
})
tempZipStorage += ".zip"
websocket_helper.broadcast_user(exec_user["user_id"], "send_logs_bootbox", {})
self.users.set_support_path(exec_user['user_id'], tempZipStorage)
self.users.set_support_path(exec_user["user_id"], tempZipStorage)
self.users.stop_prepare(exec_user['user_id'])
self.support_scheduler.remove_job('logs_'+str(exec_user["user_id"]))
self.users.stop_prepare(exec_user["user_id"])
self.support_scheduler.remove_job("logs_" + str(exec_user["user_id"]))
@staticmethod
def add_system_user():
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", False, False)
helper_users.add_user(
"system",
helper.random_string_generator(64),
"default@example.com",
False,
False,
)
def get_server_settings(self, server_id):
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
return s['server_settings']
if int(s["server_id"]) == int(server_id):
return s["server_settings"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False
def crash_detection(self, server_obj):
svr = self.get_server_obj(server_obj.server_id)
#start or stop crash detection depending upon user preference
#The below functions check to see if the server is running. They only execute if it's running.
# start or stop crash detection depending upon user preference
# The below functions check to see if the server is running.
# They only execute if it's running.
if server_obj.crash_detection == 1:
svr.start_crash_detection()
else:
@ -216,29 +258,28 @@ class Controller:
self.log_stats = results
if len(websocket_helper.clients) > 0:
websocket_helper.broadcast_user(exec_user['user_id'], 'support_status_update', results)
websocket_helper.broadcast_user(
exec_user["user_id"], "support_status_update", results
)
def send_log_status(self):
try:
return self.log_stats
except:
return {
'percent': 0,
'total_files': 0
}
return {"percent": 0, "total_files": 0}
def get_server_obj(self, server_id: Union[str, int]) -> Union[bool, Server]:
for s in self.servers_list:
if str(s['server_id']) == str(server_id):
return s['server_obj']
if str(s["server_id"]) == str(server_id):
return s["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False # TODO: Change to None
def get_server_data(self, server_id: str):
for s in self.servers_list:
if str(s['server_id']) == str(server_id):
return s['server_data_obj']
if str(s["server_id"]) == str(server_id):
return s["server_data_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False
@ -255,14 +296,11 @@ class Controller:
for s in self.servers_list:
# is the server running?
srv_obj = s['server_obj']
srv_obj = s["server_obj"]
running = srv_obj.check_running()
# if so, let's add a dictionary to the list of running servers
if running:
running_servers.append({
'id': srv_obj.server_id,
'name': srv_obj.name
})
running_servers.append({"id": srv_obj.server_id, "name": srv_obj.name})
return running_servers
@ -278,7 +316,7 @@ class Controller:
logger.info(f"Stopping Server ID {s['id']} - {s['name']}")
console.info(f"Stopping Server ID {s['id']} - {s['name']}")
self.stop_server(s['id'])
self.stop_server(s["id"])
# let's wait 2 seconds to let everything flush out
time.sleep(2)
@ -291,15 +329,23 @@ class Controller:
svr_obj = self.get_server_obj(server_id)
svr_obj.stop_threaded_server()
def create_jar_server(self, server: str, version: str, name: str, min_mem: int, max_mem: int, port: int):
def create_jar_server(
self,
server: str,
version: str,
name: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
server_dir = helper.wtol_path(server_dir)
backup_path = helper.wtol_path(backup_path)
server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
server_file = f"{server}-{version}.jar"
full_jar_path = os.path.join(server_dir, server_file)
@ -310,12 +356,14 @@ class Controller:
try:
# do a eula.txt
with open(os.path.join(server_dir, "eula.txt"), 'w', encoding='utf-8') as f:
with open(os.path.join(server_dir, "eula.txt"), "w", encoding="utf-8") as f:
f.write("eula=false")
f.close()
# setup server.properties with the port
with open(os.path.join(server_dir, "server.properties"), "w", encoding='utf-8') as f:
with open(
os.path.join(server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
@ -323,16 +371,33 @@ class Controller:
logger.error(f"Unable to create required server files due to :{e}")
return False
#must remain non-fstring due to string addtion
if helper.is_os_windows():
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
server_log_file = f"{server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop,
port, server_type='minecraft-java')
new_id = self.register_server(
name,
server_id,
server_dir,
backup_path,
server_command,
server_file,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
# download the jar
server_jar_obj.download_jar(server, version, full_jar_path, new_id)
@ -340,7 +405,7 @@ class Controller:
return new_id
@staticmethod
def verify_jar_server( server_path: str, server_jar: str):
def verify_jar_server(server_path: str, server_jar: str):
server_path = helper.get_os_understandable_path(server_path)
path_check = helper.check_path_exists(server_path)
jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
@ -356,15 +421,23 @@ class Controller:
return False
return True
def import_jar_server(self, server_name: str, server_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
def import_jar_server(
self,
server_name: str,
server_path: str,
server_jar: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
@ -376,87 +449,145 @@ class Controller:
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == 'server.properties':
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
#due to adding strings this must not be an fstring
if helper.is_os_windows():
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
server_log_file = f"{new_server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port, server_type='minecraft-java')
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_jar,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
return new_id
def import_zip_server(self, server_name: str, zip_path: str, server_jar: str, min_mem: int, max_mem: int, port: int):
def import_zip_server(
self,
server_name: str,
zip_path: str,
server_jar: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
has_properties = False
#extracts archive to temp directory
# extracts archive to temp directory
for item in os.listdir(tempDir):
if str(item) == 'server.properties':
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(tempDir, item)):
file_helper.move_file(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
file_helper.move_file(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
else:
file_helper.move_dir(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
file_helper.move_dir(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
#due to strings being added we need to leave this as not an fstring
if helper.is_os_windows():
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar "{full_jar_path}" nogui'
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = f'java -Xms{helper.float_to_string(min_mem)}M -Xmx{helper.float_to_string(max_mem)}M -jar {full_jar_path} nogui'
logger.debug('command: ' + server_command)
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
logger.debug("command: " + server_command)
server_log_file = f"{new_server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_jar,
server_log_file, server_stop, port, server_type='minecraft-java')
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_jar,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
return new_id
#************************************************************************************************
# **********************************************************************************
# BEDROCK IMPORTS
#************************************************************************************************
# **********************************************************************************
def import_bedrock_server(self, server_name: str, server_path: str, server_exe: str, port: int):
def import_bedrock_server(
self, server_name: str, server_path: str, server_exe: str, port: int
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
@ -468,96 +599,132 @@ class Controller:
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == 'server.properties':
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
#due to adding strings this must not be an fstring
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f'./{server_exe}'
logger.debug('command: ' + server_command)
server_command = f"./{server_exe}"
logger.debug("command: " + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
server_log_file, server_stop, port, server_type='minecraft-bedrock')
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_exe,
server_log_file,
server_stop,
port,
server_type="minecraft-bedrock",
)
if os.name != "nt":
if helper.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2775)
return new_id
def import_bedrock_zip_server(self, server_name: str, zip_path: str, server_exe: str, port: int):
def import_bedrock_zip_server(
self, server_name: str, zip_path: str, server_exe: str, port: int
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
has_properties = False
#extracts archive to temp directory
# extracts archive to temp directory
for item in os.listdir(tempDir):
if str(item) == 'server.properties':
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(tempDir, item)):
file_helper.move_file(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
file_helper.move_file(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
else:
file_helper.move_dir(os.path.join(tempDir, item), os.path.join(new_server_dir, item))
file_helper.move_dir(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f'ERROR IN ZIP IMPORT: {ex}')
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(f"No server.properties found on zip file import. Creating one with port selection of {str(port)}")
with open(os.path.join(new_server_dir, "server.properties"), "w", encoding='utf-8') as f:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
#due to strings being added we need to leave this as not an fstring
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f'./{server_exe}'
logger.debug('command: ' + server_command)
server_command = f"./{server_exe}"
logger.debug("command: " + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(server_name, server_id, new_server_dir, backup_path, server_command, server_exe,
server_log_file, server_stop, port, server_type='minecraft-bedrock')
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_exe,
server_log_file,
server_stop,
port,
server_type="minecraft-bedrock",
)
if os.name != "nt":
if helper.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2775)
return new_id
#************************************************************************************************
# **********************************************************************************
# BEDROCK IMPORTS END
#************************************************************************************************
# **********************************************************************************
def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
server_data = self.servers.get_server_data_by_id(old_server_id)
old_bu_path = server_data['backup_path']
old_bu_path = server_data["backup_path"]
Server_Perms_Controller.backup_role_swap(old_server_id, new_server_id)
if not helper.is_os_windows():
backup_path = helper.validate_traversal(helper.backup_path, old_bu_path)
if helper.is_os_windows():
backup_path = helper.validate_traversal(helper.wtol_path(helper.backup_path), helper.wtol_path(old_bu_path))
backup_path = helper.validate_traversal(
helper.wtol_path(helper.backup_path), helper.wtol_path(old_bu_path)
)
backup_path = helper.wtol_path(str(backup_path))
backup_path.replace(' ', '^ ')
backup_path.replace(" ", "^ ")
backup_path = Path(backup_path)
backup_path_components = list(backup_path.parts)
backup_path_components[-1] = new_uuid
@ -567,27 +734,46 @@ class Controller:
os.rmdir(new_bu_path)
backup_path.rename(new_bu_path)
def register_server(self, name: str,
server_uuid: str,
server_dir: str,
backup_path: str,
server_command: str,
server_file: str,
server_log_file: str,
server_stop: str,
server_port: int,
server_type: str):
def register_server(
self,
name: str,
server_uuid: str,
server_dir: str,
backup_path: str,
server_command: str,
server_file: str,
server_log_file: str,
server_stop: str,
server_port: int,
server_type: str,
):
# put data in the db
new_id = self.servers.create_server(
name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_type, server_port)
name,
server_uuid,
server_dir,
backup_path,
server_command,
server_file,
server_log_file,
server_stop,
server_type,
server_port,
)
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
try:
# place a file in the dir saying it's owned by crafty
with open(os.path.join(server_dir, "crafty_managed.txt"), 'w', encoding='utf-8') as f:
with open(
os.path.join(server_dir, "crafty_managed.txt"),
"w",
encoding="utf-8",
) as f:
f.write(
"The server is managed by Crafty Controller.\n Leave this directory/files alone please")
"The server is managed by Crafty Controller.\n "
"Leave this directory/files alone please"
)
f.close()
except Exception as e:
@ -604,28 +790,42 @@ class Controller:
for s in self.servers_list:
# if this is the droid... im mean server we are looking for...
if str(s['server_id']) == str(server_id):
if str(s["server_id"]) == str(server_id):
server_data = self.get_server_data(server_id)
server_name = server_data['server_name']
server_name = server_data["server_name"]
logger.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
console.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
srv_obj = s['server_obj']
srv_obj = s["server_obj"]
running = srv_obj.check_running()
if running:
self.stop_server(server_id)
if files:
try:
file_helper.del_dirs(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['path']))
file_helper.del_dirs(
helper.get_os_understandable_path(
self.servers.get_server_data_by_id(server_id)["path"]
)
)
except Exception as e:
logger.error(f"Unable to delete server files for server with ID: {server_id} with error logged: {e}")
if helper.check_path_exists(self.servers.get_server_data_by_id(server_id)['backup_path']):
file_helper.del_dirs(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['backup_path']))
logger.error(
f"Unable to delete server files for server with ID: "
f"{server_id} with error logged: {e}"
)
if helper.check_path_exists(
self.servers.get_server_data_by_id(server_id)["backup_path"]
):
file_helper.del_dirs(
helper.get_os_understandable_path(
self.servers.get_server_data_by_id(server_id)[
"backup_path"
]
)
)
#Cleanup scheduled tasks
# Cleanup scheduled tasks
try:
helpers_management.delete_scheduled_task_by_server(server_id)
except DoesNotExist:
@ -637,6 +837,7 @@ class Controller:
self.servers_list.pop(counter)
counter += 1
@staticmethod
def clear_unexecuted_commands():
helpers_management.clear_unexecuted_commands()

View File

@ -17,24 +17,29 @@ except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
database = SqliteDatabase(helper.db_path, pragmas = {
'journal_mode': 'wal',
'cache_size': -1024 * 10})
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')
username = default_data.get("username", "admin")
password = default_data.get("password", "crafty")
users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
users_helper.add_user(
username=username,
password=password,
email="default@example.com",
superuser=True,
)
@staticmethod
def is_fresh_install():
@ -45,11 +50,12 @@ class db_builder:
except:
return True
class db_shortcuts:
#************************************************************************************************
# **********************************************************************************
# Generic Databse Methods
#************************************************************************************************
# **********************************************************************************
@staticmethod
def return_rows(query):
rows = []
@ -69,8 +75,8 @@ class db_shortcuts:
return data
#************************************************************************************************
# **********************************************************************************
# Static Accessors
#************************************************************************************************
# **********************************************************************************
installer = db_builder()
db_helper = db_shortcuts()

View File

@ -15,8 +15,10 @@ try:
import peewee
from playhouse.migrate import (
SqliteMigrator,
Operation, SQL, SqliteDatabase,
make_index_name
Operation,
SQL,
SqliteDatabase,
make_index_name,
)
except ModuleNotFoundError as e:
@ -24,7 +26,7 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
MIGRATE_TABLE = 'migratehistory'
MIGRATE_TABLE = "migratehistory"
MIGRATE_TEMPLATE = '''# Generated by database migrator
import peewee
@ -70,6 +72,7 @@ def get_model(method):
if isinstance(model, str):
return method(migrator, migrator.table_dict[model], *args, **kwargs)
return method(migrator, model, *args, **kwargs)
return wrapper
@ -133,11 +136,17 @@ class Migrator(object):
"""
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))
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))
self.operations.append(
self.migrator.add_index(
model._meta.table_name, (field.column_name,), unique=True
)
)
return model
@get_model
@ -145,19 +154,22 @@ class Migrator(object):
"""
Removes fields from model.
"""
fields = [field for field in model._meta.fields.values()
if field.name in names]
fields = [field for field in model._meta.fields.values() if field.name in names]
for field in fields:
self.__del_field__(model, field)
if field.unique:
# Drop unique index
index_name = make_index_name(
model._meta.table_name, [field.column_name])
self.operations.append(self.migrator.drop_index(
model._meta.table_name, index_name))
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))
model._meta.table_name, field.column_name, cascade=False
)
)
return model
def __del_field__(self, model: peewee.Model, field: peewee.Field):
@ -169,12 +181,14 @@ class Migrator(object):
if isinstance(field, peewee.ForeignKeyField):
obj_id_name = field.column_name
if field.column_name == field.name:
obj_id_name += '_id'
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:
def rename_column(
self, model: peewee.Model, old_name: str, new_name: str
) -> peewee.Model:
"""
Renames field in model.
"""
@ -185,9 +199,10 @@ class Migrator(object):
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))
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
@ -203,7 +218,9 @@ class Migrator(object):
return model
@get_model
def add_index(self, model: peewee.Model, *columns: str, unique=False) -> peewee.Model:
def add_index(
self, model: peewee.Model, *columns: str, unique=False
) -> peewee.Model:
"""Create indexes."""
model._meta.indexes.append((columns, unique))
columns_ = []
@ -215,11 +232,12 @@ class Migrator(object):
field.index = not unique
if isinstance(field, peewee.ForeignKeyField):
col = col + '_id'
col = col + "_id"
columns_.append(col)
self.operations.append(self.migrator.add_index(
model._meta.table_name, columns_, unique=unique))
self.operations.append(
self.migrator.add_index(model._meta.table_name, columns_, unique=unique)
)
return model
@get_model
@ -235,13 +253,15 @@ class Migrator(object):
field.unique = field.index = False
if isinstance(field, peewee.ForeignKeyField):
col = col + '_id'
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))
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
@ -250,8 +270,9 @@ class Migrator(object):
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))
self.operations.append(
self.migrator.add_not_null(model._meta.table_name, field.column_name)
)
return model
@get_model
@ -260,17 +281,21 @@ class Migrator(object):
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))
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:
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))
self.operations.append(
self.migrator.apply_default(model._meta.table_name, name, field)
)
return model
@ -283,7 +308,7 @@ class MigrationManager(object):
Initializes the migration manager.
"""
if not isinstance(database, (peewee.Database, peewee.Proxy)):
raise RuntimeError('Invalid database: {}'.format(database))
raise RuntimeError("Invalid database: {}".format(database))
self.database = database
@cached_property
@ -292,7 +317,7 @@ class MigrationManager(object):
Initialize and cache the MigrationHistory model.
"""
MigrateHistory._meta.database = self.database
MigrateHistory._meta.table_name = 'migratehistory'
MigrateHistory._meta.table_name = "migratehistory"
MigrateHistory._meta.schema = None
MigrateHistory.create_table(True)
return MigrateHistory
@ -310,10 +335,13 @@ class MigrationManager(object):
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))
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))
return sorted(
f[:-3] for f in os.listdir(helper.migration_dir) if self.filemask.match(f)
)
@property
def diff(self) -> t.List[str]:
@ -333,24 +361,27 @@ class MigrationManager(object):
self.up_one(name, migrator, True)
return migrator
def compile(self, name, migrate='', rollback=''):
def compile(self, name, migrate="", rollback=""):
"""
Compiles a migration.
"""
name = datetime.utcnow().strftime('%Y%m%d%H%M%S') + '_' + name
filename = name + '.py'
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))
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]:
def create(self, name: str = "auto", auto: bool = False) -> t.Optional[str]:
"""
Creates a migration.
"""
migrate = rollback = ''
migrate = rollback = ""
if auto:
raise NotImplementedError
@ -367,14 +398,14 @@ class MigrationManager(object):
"""
Runs all unapplied migrations.
"""
logger.info('Starting migrations')
console.info('Starting 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')
logger.info("There is nothing to migrate")
console.info("There is nothing to migrate")
return done
migrator = self.migrator
@ -392,16 +423,19 @@ class MigrationManager(object):
call_params = dict()
if helper.is_os_windows() and sys.version_info >= (3, 0):
# if system is windows - force utf-8 encoding
call_params['encoding'] = 'utf-8'
with open(os.path.join(helper.migration_dir, name + '.py'), **call_params) as f:
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)
code = compile(code, "<string>", "exec", dont_inherit=True)
exec(code, scope, None)
return scope.get('migrate', lambda m, d: None), scope.get('rollback', lambda m, d: None)
return scope.get("migrate", lambda m, d: None), scope.get(
"rollback", lambda m, d: None
)
def up_one(self, name: str, migrator: Migrator,
fake: bool = False, rollback: bool = False) -> str:
def up_one(
self, name: str, migrator: Migrator, fake: bool = False, rollback: bool = False
) -> str:
"""
Runs a migration with a given name.
"""
@ -429,8 +463,8 @@ class MigrationManager(object):
except Exception:
self.database.rollback()
operation_name = 'Rollback' if rollback else 'Migration'
logger.exception('{} failed: {}'.format(operation_name, name))
operation_name = "Rollback" if rollback else "Migration"
logger.exception("{} failed: {}".format(operation_name, name))
raise
def down(self):
@ -438,10 +472,10 @@ class MigrationManager(object):
Rolls back migrations.
"""
if not self.done:
raise RuntimeError('No migrations are found.')
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))
logger.warning("Rolled back migration: {}".format(name))

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -23,23 +23,24 @@ try:
except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
logger = logging.getLogger('apscheduler')
scheduler_intervals = { 'seconds',
'minutes',
'hours',
'days',
'weeks',
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday'
}
logger = logging.getLogger("apscheduler")
scheduler_intervals = {
"seconds",
"minutes",
"hours",
"days",
"weeks",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
}
class TasksManager:
def __init__(self, controller):
self.controller = controller
self.tornado = Webserver(controller, self)
@ -49,21 +50,30 @@ class TasksManager:
self.users_controller = Users_Controller()
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
self.webserver_thread = threading.Thread(
target=self.tornado.run_tornado, daemon=True, name="tornado_thread"
)
self.main_thread_exiting = False
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
self.schedule_thread = threading.Thread(
target=self.scheduler_thread, daemon=True, name="scheduler"
)
self.log_watcher_thread = threading.Thread(target=self.log_watcher, daemon=True, name="log_watcher")
self.log_watcher_thread = threading.Thread(
target=self.log_watcher, daemon=True, name="log_watcher"
)
self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher")
self.command_thread = threading.Thread(
target=self.command_watcher, daemon=True, name="command_watcher"
)
self.realtime_thread = threading.Thread(target=self.realtime, daemon=True, name="realtime")
self.realtime_thread = threading.Thread(
target=self.realtime, daemon=True, name="realtime"
)
self.reload_schedule_from_db()
def get_main_thread_run_status(self):
return self.main_thread_exiting
@ -81,16 +91,19 @@ class TasksManager:
try:
svr = self.controller.get_server_obj(c.server_id)
except:
logger.error("Server value requested does note exist purging item from waiting commands.")
logger.error(
"Server value requested does note exist! "
"Purging item from waiting commands."
)
management_helper.mark_command_complete(c.command_id)
user_id = c.user_id
command = c.command
if command == 'start_server':
if command == "start_server":
svr.run_threaded_server(user_id)
elif command == 'stop_server':
elif command == "stop_server":
svr.stop_threaded_server()
elif command == "restart_server":
@ -125,7 +138,9 @@ class TasksManager:
self.tornado.stop_web_server()
console.info("Waiting 3 seconds")
time.sleep(3)
self.webserver_thread = threading.Thread(target=self.tornado.run_tornado, daemon=True, name='tornado_thread')
self.webserver_thread = threading.Thread(
target=self.tornado.run_tornado, daemon=True, name="tornado_thread"
)
self.start_webserver()
def stop_webserver(self):
@ -148,64 +163,79 @@ class TasksManager:
def scheduler_thread(self):
schedules = management_helper.get_schedules_enabled()
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
#self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1')
# self.scheduler.add_job(
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
# )
#load schedules from DB
# load schedules from DB
for schedule in schedules:
if schedule.interval != 'reaction':
if schedule.interval != "reaction":
if schedule.cron_string != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(schedule.cron_string,
timezone=str(self.tz)),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
self.scheduler.add_job(
management_helper.add_command,
CronTrigger.from_crontab(
schedule.cron_string, timezone=str(self.tz)
),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
# remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(schedule.schedule_id)
else:
if schedule.interval_type == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(schedule.interval),
id = str(schedule.schedule_id),
args = [schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
elif schedule.interval_type == 'days':
curr_time = schedule.start_time.split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(schedule.interval),
hour=curr_time[0],
minute=curr_time[1],
id=str(schedule.schedule_id),
args=[schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command]
)
if schedule.interval_type == "hours":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute=0,
hour="*/" + str(schedule.interval),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
elif schedule.interval_type == "minutes":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute="*/" + str(schedule.interval),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
elif schedule.interval_type == "days":
curr_time = schedule.start_time.split(":")
self.scheduler.add_job(
management_helper.add_command,
"cron",
day="*/" + str(schedule.interval),
hour=curr_time[0],
minute=curr_time[1],
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
self.scheduler.start()
jobs = self.scheduler.get_jobs()
logger.info("Loaded schedules. Current enabled schedules: ")
@ -214,76 +244,91 @@ class TasksManager:
def schedule_job(self, job_data):
sch_id = management_helper.create_scheduled_task(
job_data['server_id'],
job_data['action'],
job_data['interval'],
job_data['interval_type'],
job_data['start_time'],
job_data['command'],
job_data["server_id"],
job_data["action"],
job_data["interval"],
job_data["interval_type"],
job_data["start_time"],
job_data["command"],
"None",
job_data['enabled'],
job_data['one_time'],
job_data['cron_string'],
job_data['parent'],
job_data['delay'])
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
if str(job_data['parent']) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {'parent':None})
#Check to see if it's enabled and is not a chain reaction.
if job_data['enabled'] and job_data['interval_type'] != 'reaction':
if job_data['cron_string'] != "":
job_data["enabled"],
job_data["one_time"],
job_data["cron_string"],
job_data["parent"],
job_data["delay"],
)
# Checks to make sure some doofus didn't actually make the newly
# created task a child of itself.
if str(job_data["parent"]) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {"parent": None})
# Check to see if it's enabled and is not a chain reaction.
if job_data["enabled"] and job_data["interval_type"] != "reaction":
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(job_data['cron_string'],
timezone=str(self.tz)),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
self.scheduler.add_job(
management_helper.add_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.warning("Removing failed task from DB.")
logger.error(f"Failed to schedule task with error: {e}.")
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
# remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(sch_id)
else:
if job_data['interval_type'] == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'days':
curr_time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(job_data['interval']),
hour = curr_time[0],
minute = curr_time[1],
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']],
)
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute=0,
hour="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
elif job_data["interval_type"] == "minutes":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
self.scheduler.add_job(
management_helper.add_command,
"cron",
day="*/" + str(job_data["interval"]),
hour=curr_time[0],
minute=curr_time[1],
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
logger.info("Added job. Current enabled schedules: ")
jobs = self.scheduler.get_jobs()
for item in jobs:
@ -292,134 +337,191 @@ class TasksManager:
def remove_all_server_tasks(self, server_id):
schedules = management_helper.get_schedules_by_server(server_id)
for schedule in schedules:
if schedule.interval != 'reaction':
if schedule.interval != "reaction":
self.remove_job(schedule.schedule_id)
def remove_job(self, sch_id):
job = management_helper.get_scheduled_task_model(sch_id)
for schedule in management_helper.get_child_schedules(sch_id):
management_helper.update_scheduled_task(schedule.schedule_id, {'parent':None})
management_helper.update_scheduled_task(
schedule.schedule_id, {"parent": None}
)
management_helper.delete_scheduled_task(sch_id)
if job.enabled and job.interval_type != 'reaction':
if job.enabled and job.interval_type != "reaction":
self.scheduler.remove_job(str(sch_id))
logger.info(f"Job with ID {sch_id} was deleted.")
else:
logger.info(f"Job with ID {sch_id} was deleted from DB, but was not enabled."
+ "Not going to try removing something that doesn't exist from active schedules.")
logger.info(
f"Job with ID {sch_id} was deleted from DB, but was not enabled."
f"Not going to try removing something "
f"that doesn't exist from active schedules."
)
def update_job(self, sch_id, job_data):
management_helper.update_scheduled_task(sch_id, job_data)
#Checks to make sure some doofus didn't actually make the newly created task a child of itself.
if str(job_data['parent']) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {'parent':None})
# Checks to make sure some doofus didn't actually make the newly
# created task a child of itself.
if str(job_data["parent"]) == str(sch_id):
management_helper.update_scheduled_task(sch_id, {"parent": None})
try:
if job_data['interval'] != 'reaction':
if job_data["interval"] != "reaction":
self.scheduler.remove_job(str(sch_id))
except:
logger.info("No job found in update job. Assuming it was previously disabled. Starting new job.")
logger.info(
"No job found in update job. "
"Assuming it was previously disabled. Starting new job."
)
if job_data['enabled']:
if job_data['interval'] != 'reaction':
if job_data['cron_string'] != "":
if job_data["enabled"]:
if job_data["interval"] != "reaction":
if job_data["cron_string"] != "":
try:
self.scheduler.add_job(management_helper.add_command,
CronTrigger.from_crontab(job_data['cron_string'],
timezone=str(self.tz)),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
self.scheduler.add_job(
management_helper.add_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
except Exception as e:
console.error(f"Failed to schedule task with error: {e}.")
console.info("Removing failed task from DB.")
management_helper.delete_scheduled_task(sch_id)
else:
if job_data['interval_type'] == 'hours':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = 0,
hour = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'minutes':
self.scheduler.add_job(management_helper.add_command,
'cron',
minute = '*/'+str(job_data['interval']),
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
elif job_data['interval_type'] == 'days':
curr_time = job_data['start_time'].split(':')
self.scheduler.add_job(management_helper.add_command,
'cron',
day = '*/'+str(job_data['interval']),
hour = curr_time[0],
minute = curr_time[1],
id=str(sch_id),
args=[job_data['server_id'],
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
job_data['command']]
)
if job_data["interval_type"] == "hours":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute=0,
hour="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
elif job_data["interval_type"] == "minutes":
self.scheduler.add_job(
management_helper.add_command,
"cron",
minute="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
self.scheduler.add_job(
management_helper.add_command,
"cron",
day="*/" + str(job_data["interval"]),
hour=curr_time[0],
minute=curr_time[1],
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
],
)
else:
try:
self.scheduler.get_job(str(sch_id))
self.scheduler.remove_job(str(sch_id))
except:
logger.info(f"APScheduler found no scheduled job on schedule update for schedule with id: {sch_id} Assuming it was already disabled.")
logger.info(
f"APScheduler found no scheduled job on schedule update for "
f"schedule with id: {sch_id} Assuming it was already disabled."
)
def schedule_watcher(self, event):
if not event.exception:
if str(event.job_id).isnumeric():
task = management_helper.get_scheduled_task_model(int(event.job_id))
management_helper.add_to_audit_log_raw('system', users_helper.get_user_id_by_name('system'), task.server_id,
f"Task with id {task.schedule_id} completed successfully", '127.0.0.1')
#check if the task is a single run.
management_helper.add_to_audit_log_raw(
"system",
users_helper.get_user_id_by_name("system"),
task.server_id,
f"Task with id {task.schedule_id} completed successfully",
"127.0.0.1",
)
# check if the task is a single run.
if task.one_time:
self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...")
#check for any child tasks for this. It's kind of backward, but this makes DB management a lot easier. One to one instead of one to many.
for schedule in management_helper.get_child_schedules_by_server(task.schedule_id, task.server_id):
#event job ID's are strings so we need to look at this as the same data type.
# check for any child tasks for this. It's kind of backward,
# but this makes DB management a lot easier. One to one
# instead of one to many.
for schedule in management_helper.get_child_schedules_by_server(
task.schedule_id, task.server_id
):
# event job ID's are strings so we need to look at
# this as the same data type.
if str(schedule.parent) == str(event.job_id):
if schedule.enabled:
delaytime = datetime.datetime.now() + datetime.timedelta(seconds=schedule.delay)
self.scheduler.add_job(management_helper.add_command, 'date', run_date=delaytime, id=str(schedule.schedule_id),
args=[schedule.server_id,
self.users_controller.get_id_by_name('system'),
'127.0.0.1',
schedule.command])
delaytime = datetime.datetime.now() + datetime.timedelta(
seconds=schedule.delay
)
self.scheduler.add_job(
management_helper.add_command,
"date",
run_date=delaytime,
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
],
)
else:
logger.info("Event job ID is not numerical. Assuming it's stats - not stored in DB. Moving on.")
logger.info(
"Event job ID is not numerical. Assuming it's stats "
"- not stored in DB. Moving on."
)
else:
logger.error(f"Task failed with error: {event.exception}")
def start_stats_recording(self):
stats_update_frequency = helper.get_setting('stats_update_frequency')
logger.info(f"Stats collection frequency set to {stats_update_frequency} seconds")
console.info(f"Stats collection frequency set to {stats_update_frequency} seconds")
stats_update_frequency = helper.get_setting("stats_update_frequency")
logger.info(
f"Stats collection frequency set to {stats_update_frequency} seconds"
)
console.info(
f"Stats collection frequency set to {stats_update_frequency} seconds"
)
# one for now,
self.controller.stats.record_stats()
# one for later
self.scheduler.add_job(self.controller.stats.record_stats, 'interval', seconds=stats_update_frequency, id="stats")
self.scheduler.add_job(
self.controller.stats.record_stats,
"interval",
seconds=stats_update_frequency,
id="stats",
)
def serverjar_cache_refresher(self):
logger.info("Refreshing serverjars.com cache on start")
server_jar_obj.refresh_cache()
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
self.scheduler.add_job(server_jar_obj.refresh_cache, 'interval', hours=12, id="serverjars")
self.scheduler.add_job(
server_jar_obj.refresh_cache, "interval", hours=12, id="serverjars"
)
def realtime(self):
loop = asyncio.new_event_loop()
@ -429,24 +531,38 @@ class TasksManager:
while True:
if host_stats.get('cpu_usage') != \
management_helper.get_latest_hosts_stats().get('cpu_usage') or \
host_stats.get('mem_percent') != \
management_helper.get_latest_hosts_stats().get('mem_percent'):
if host_stats.get(
"cpu_usage"
) != management_helper.get_latest_hosts_stats().get(
"cpu_usage"
) or host_stats.get(
"mem_percent"
) != management_helper.get_latest_hosts_stats().get(
"mem_percent"
):
# Stats are different
host_stats = management_helper.get_latest_hosts_stats()
if len(websocket_helper.clients) > 0:
# There are clients
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'),
'cpu_max_freq': host_stats.get('cpu_max_freq'),
'mem_percent': host_stats.get('mem_percent'),
'mem_usage': host_stats.get('mem_usage')
})
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"),
"cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"),
},
)
def log_watcher(self):
self.controller.servers.check_for_old_logs()
self.scheduler.add_job(self.controller.servers.check_for_old_logs, 'interval', hours=6, id="log-mgmt")
self.scheduler.add_job(
self.controller.servers.check_for_old_logs,
"interval",
hours=6,
id="log-mgmt",
)

View File

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

View File

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

View File

@ -4,33 +4,46 @@ import re
from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__)
bearer_pattern = re.compile(r'^Bearer', flags=re.IGNORECASE)
bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE)
class ApiHandler(BaseHandler):
def return_response(self, status: int, data: dict):
# Define a standardized response
self.set_status(status)
self.write(data)
def access_denied(self, user, reason=''):
def access_denied(self, user, reason=""):
if reason:
reason = ' because ' + reason
logger.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
self.finish(self.return_response(403, {
'error':'ACCESS_DENIED',
'info':'You were denied access to the requested resource'
}))
reason = " because " + reason
logger.info(
"User %s from IP %s was denied access to the API route "
+ self.request.path
+ reason,
user,
self.get_remote_ip(),
)
self.finish(
self.return_response(
403,
{
"error": "ACCESS_DENIED",
"info": "You were denied access to the requested resource",
},
)
)
def authenticate_user(self) -> bool:
try:
logger.debug("Searching for specified token")
api_token = self.get_argument('token', '')
if api_token is None and self.request.headers.get('Authorization'):
api_token = bearer_pattern.sub('', self.request.headers.get('Authorization'))
api_token = self.get_argument("token", "")
if api_token is None and self.request.headers.get("Authorization"):
api_token = bearer_pattern.sub(
"", self.request.headers.get("Authorization")
)
elif api_token is None:
api_token = self.get_cookie('token')
api_token = self.get_cookie("token")
user_data = self.controller.users.get_user_by_api_token(api_token)
logger.debug("Checking results")
@ -39,17 +52,22 @@ class ApiHandler(BaseHandler):
logger.info(f"User {user_data['username']} has authenticated to API")
# TODO: Role check
return True # This is to set the "authenticated"
return True # This is to set the "authenticated"
else:
logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token")
return False
except Exception as e:
logger.warning("An error occured while authenticating an API user: %s", e)
self.finish(self.return_response(403, {
'error':'ACCESS_DENIED',
'info':'An error occured while authenticating the user'
}))
self.finish(
self.return_response(
403,
{
"error": "ACCESS_DENIED",
"info": "An error occured while authenticating the user",
},
)
)
return False

View File

@ -1,9 +1,5 @@
import logging
from typing import (
Union,
List,
Optional, Tuple, Dict, Any
)
from typing import Union, List, Optional, Tuple, Dict, Any
from app.classes.models.users import ApiKeys
from app.classes.shared.authentication import authentication
@ -19,25 +15,33 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
class BaseHandler(tornado.web.RequestHandler):
nobleach = {bool, type(None)}
redactables = ("pass", "api")
# noinspection PyAttributeOutsideInit
def initialize(self, controller: 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
def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
self.request.remote_ip
remote_ip = (
self.request.headers.get("X-Real-IP")
or self.request.headers.get("X-Forwarded-For")
or self.request.remote_ip
)
return remote_ip
current_user: Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]
def get_current_user(self) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
def get_current_user(
self,
) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
return authentication.check(self.get_cookie("token"))
def autobleach(self, name, text):
@ -54,11 +58,13 @@ class BaseHandler(tornado.web.RequestHandler):
return bleach.clean(text)
def get_argument(
self,
name: str,
default: Union[None, str, tornado.web._ArgDefaultMarker] = tornado.web._ARG_DEFAULT,
strip: bool = True,
) -> Optional[str]:
self,
name: str,
default: Union[
None, str, tornado.web._ArgDefaultMarker
] = tornado.web._ARG_DEFAULT,
strip: bool = True,
) -> Optional[str]:
arg = self._get_argument(name, default, self.request.arguments, strip)
return self.autobleach(name, arg)

View File

@ -4,6 +4,7 @@ from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__)
class DefaultHandler(BaseHandler):
# Override prepare() instead of get() to cover all possible HTTP methods.
@ -18,5 +19,5 @@ class DefaultHandler(BaseHandler):
else:
self.redirect(
"/public/login",
#translate=self.translator.translate,
# translate=self.translator.translate,
)

View File

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

View File

@ -11,22 +11,23 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
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]
url = "https://" + url_list[0]
else:
url = 'https://' + url
db_port = helper.get_setting('https_port')
url = "https://" + url
db_port = helper.get_setting("https_port")
try:
resp = requests.get(url + ":" + str(port))
resp.raise_for_status()
except Exception:
port = db_port
self.redirect(url+":"+str(port))
self.redirect(url + ":" + str(port))
class HTTPHandlerPage(BaseHandler):
@ -35,13 +36,13 @@ class HTTPHandlerPage(BaseHandler):
port = 443
url_list = url.split(":")
if url_list[0] != "":
url = 'https://' + url_list[0]
url = "https://" + url_list[0]
else:
url = 'https://' + url
db_port = helper.get_setting('https_port')
url = "https://" + url
db_port = helper.get_setting("https_port")
try:
resp = requests.get(url + ":" + str(port))
resp.raise_for_status()
except Exception:
port = db_port
self.redirect(url+":"+str(port))
self.redirect(url + ":" + str(port))

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -20,13 +20,13 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
class ServerHandler(BaseHandler):
class ServerHandler(BaseHandler):
@tornado.web.authenticated
def get(self, page):
# pylint: disable=unused-variable
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
superuser = exec_user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
@ -34,61 +34,82 @@ class ServerHandler(BaseHandler):
if superuser:
defined_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User")
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
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:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user["user_id"])
defined_servers = self.controller.servers.get_authorized_servers(exec_user["user_id"])
exec_user_crafty_permissions = (
self.controller.crafty_perms.get_crafty_permissions_list(
exec_user["user_id"]
)
)
defined_servers = self.controller.servers.get_authorized_servers(
exec_user["user_id"]
)
list_roles = []
for r in exec_user['roles']:
for r in exec_user["roles"]:
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']))
exec_user_role.add(role["role_name"])
list_roles.append(self.controller.roles.get_role(role["role_id"]))
template = "public/404.html"
page_data = {
'version_data': helper.get_version_string(),
'user_data': exec_user,
'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,
"version_data": helper.get_version_string(),
"user_data": exec_user,
"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()))
"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': self.controller.management.get_latest_hosts_stats(),
'menu_servers': defined_servers,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
'lang_page': helper.getLangPage(self.controller.users.get_user_lang_by_id(exec_user["user_id"])),
'api_key': {
'name': api_key.name,
'created': api_key.created,
'server_permissions': api_key.server_permissions,
'crafty_permissions': api_key.crafty_permissions,
'superuser': api_key.superuser
} if api_key is not None else None,
'superuser': superuser
"hosts_data": self.controller.management.get_latest_hosts_stats(),
"menu_servers": defined_servers,
"show_contribute": helper.get_setting("show_contribute_link", True),
"lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
"lang_page": helper.getLangPage(
self.controller.users.get_user_lang_by_id(exec_user["user_id"])
),
"api_key": {
"name": api_key.name,
"created": api_key.created,
"server_permissions": api_key.server_permissions,
"crafty_permissions": api_key.crafty_permissions,
"superuser": api_key.superuser,
}
if api_key is not None
else None,
"superuser": superuser,
}
if helper.get_setting("allow_nsfw_profile_pictures"):
if helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x"
else:
rating = "g"
if exec_user['email'] != 'default@example.com' or "":
g = libgravatar.Gravatar(libgravatar.sanitize_email(exec_user['email']))
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
if exec_user["email"] != "default@example.com" or "":
g = libgravatar.Gravatar(libgravatar.sanitize_email(exec_user["email"]))
url = g.get_image(
size=80,
default="404",
force_default=False,
rating=rating,
filetype_extension=False,
use_ssl=True,
) # + "?d=404"
if requests.head(url).status_code != 404:
profile_url = url
else:
@ -96,22 +117,34 @@ class ServerHandler(BaseHandler):
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
page_data['user_image'] = profile_url
page_data["user_image"] = profile_url
if superuser:
page_data['roles'] = list_roles
page_data["roles"] = list_roles
if page == "step1":
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
if not superuser and not self.controller.crafty_perms.can_create_server(
exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return
page_data['server_types'] = server_jar_obj.get_serverjar_data()
page_data['js_server_types'] = json.dumps(server_jar_obj.get_serverjar_data())
page_data["server_types"] = server_jar_obj.get_serverjar_data()
page_data["js_server_types"] = json.dumps(
server_jar_obj.get_serverjar_data()
)
template = "server/wizard.html"
if page == "bedrock_step1":
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
if not superuser and not self.controller.crafty_perms.can_create_server(
exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return
template = "server/bedrock_wizard.html"
@ -126,17 +159,19 @@ class ServerHandler(BaseHandler):
def post(self, page):
# pylint: disable=unused-variable
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
superuser = exec_user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
template = "public/404.html"
page_data = {
'version_data': "version_data_here", # TODO
'user_data': exec_user,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
'lang_page': helper.getLangPage(self.controller.users.get_user_lang_by_id(exec_user["user_id"]))
"version_data": "version_data_here", # TODO
"user_data": exec_user,
"show_contribute": helper.get_setting("show_contribute_link", True),
"lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
"lang_page": helper.getLangPage(
self.controller.users.get_user_lang_by_id(exec_user["user_id"])
),
}
if page == "command":
@ -145,53 +180,68 @@ class ServerHandler(BaseHandler):
if server_id is not None:
if command == "clone_server":
def is_name_used(name):
for server in self.controller.servers.get_all_defined_servers():
if server['server_name'] == name:
if server["server_name"] == name:
return True
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)"
server_data = self.controller.servers.get_server_data_by_id(
server_id
)
server_uuid = server_data.get("server_uuid")
new_server_name = server_data.get("server_name") + " (Copy)"
name_counter = 1
while is_name_used(new_server_name):
name_counter += 1
new_server_name = server_data.get('server_name') + f" (Copy {name_counter})"
new_server_name = (
server_data.get("server_name") + f" (Copy {name_counter})"
)
new_server_uuid = helper.create_uuid()
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)):
while os.path.exists(
os.path.join(helper.servers_dir, new_server_uuid)
):
new_server_uuid = helper.create_uuid()
new_server_path = os.path.join(helper.servers_dir, new_server_uuid)
# copy the old server
file_helper.copy_dir(server_data.get('path'), new_server_path)
file_helper.copy_dir(server_data.get("path"), new_server_path)
# TODO get old server DB data to individual variables
stop_command = server_data.get('stop_command')
new_server_command = str(server_data.get('execution_command')).replace(server_uuid, new_server_uuid)
new_executable = server_data.get('executable')
new_server_log_file = str(helper.get_os_understandable_path(server_data.get('log_path'))).replace(server_uuid, new_server_uuid)
server_port = server_data.get('server_port')
server_type = server_data.get('type')
stop_command = server_data.get("stop_command")
new_server_command = str(
server_data.get("execution_command")
).replace(server_uuid, new_server_uuid)
new_executable = server_data.get("executable")
new_server_log_file = str(
helper.get_os_understandable_path(server_data.get("log_path"))
).replace(server_uuid, new_server_uuid)
server_port = server_data.get("server_port")
server_type = server_data.get("type")
self.controller.servers.create_server(new_server_name,
new_server_uuid,
new_server_path,
"",
new_server_command,
new_executable,
new_server_log_file,
stop_command,
server_type,
server_port)
self.controller.servers.create_server(
new_server_name,
new_server_uuid,
new_server_path,
"",
new_server_command,
new_executable,
new_server_log_file,
stop_command,
server_type,
server_port,
)
self.controller.init_all_servers()
return
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), command)
self.controller.management.send_command(
exec_user["user_id"], server_id, self.get_remote_ip(), command
)
if page == "step1":
@ -199,82 +249,122 @@ class ServerHandler(BaseHandler):
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', ''))
max_mem = bleach.clean(self.get_argument('max_memory', ''))
port = bleach.clean(self.get_argument('port', ''))
import_type = bleach.clean(self.get_argument('create_type', ''))
import_server_path = bleach.clean(self.get_argument('server_path', ''))
import_server_jar = bleach.clean(self.get_argument('server_jar', ''))
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", ""))
max_mem = bleach.clean(self.get_argument("max_memory", ""))
port = bleach.clean(self.get_argument("port", ""))
import_type = bleach.clean(self.get_argument("create_type", ""))
import_server_path = bleach.clean(self.get_argument("server_path", ""))
import_server_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":
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 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!")
self.redirect(
"/panel/error?error=Server path or Server Jar not found!"
)
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['user_id'],
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
elif import_type == 'import_zip':
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["user_id"],
f'imported a jar server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
elif import_type == "import_zip":
# here import_server_path means the zip path
zip_path = bleach.clean(self.get_argument('root_path'))
zip_path = bleach.clean(self.get_argument("root_path"))
good_path = helper.check_path_exists(zip_path)
if not good_path:
self.redirect("/panel/error?error=Temp path not found!")
return
new_server_id = self.controller.import_zip_server(server_name, zip_path, import_server_jar, min_mem, max_mem, port)
new_server_id = self.controller.import_zip_server(
server_name, zip_path, import_server_jar, min_mem, max_mem, port
)
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" +
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}")
self.redirect(
f"/panel/error?error=Zip file not accessible! "
f"You can fix this permissions issue with "
f"sudo chown -R crafty:crafty {import_server_path} "
f"And sudo chmod 2775 -R {import_server_path}"
)
return
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
#deletes temp dir
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f'imported a zip server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
# deletes temp dir
file_helper.del_dirs(zip_path)
else:
if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data")
return
server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar
# TODO: add server type check here and call the correct server
# add functions if not a jar
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"",
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
new_server_id = self.controller.create_jar_server(
server_type, server_version, server_name, min_mem, max_mem, port
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"created a {server_version} {str(server_type).capitalize()}"
f' server named "{server_name}"',
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip(),
)
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
# These lines create a new Role for the Server with full permissions
# and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}")
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
new_server_uuid = self.controller.servers.get_server_data_by_id(
new_server_id
).get("server_uuid")
role_id = self.controller.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}"
)
self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.users.add_role_to_user(
exec_user["user_id"], role_id
)
self.controller.crafty_perms.add_server_creation(
exec_user["user_id"]
)
else:
for role in captured_roles:
role_id = role
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.stats.record_stats()
self.redirect("/panel/dashboard")
@ -284,80 +374,115 @@ class ServerHandler(BaseHandler):
user_roles = self.controller.roles.get_all_roles()
else:
user_roles = self.controller.roles.get_all_roles()
server = bleach.clean(self.get_argument('server', ''))
server_name = bleach.clean(self.get_argument('server_name', ''))
port = bleach.clean(self.get_argument('port', ''))
import_type = bleach.clean(self.get_argument('create_type', ''))
import_server_path = bleach.clean(self.get_argument('server_path', ''))
import_server_exe = bleach.clean(self.get_argument('server_jar', ''))
server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument("server_name", ""))
port = bleach.clean(self.get_argument("port", ""))
import_type = bleach.clean(self.get_argument("create_type", ""))
import_server_path = bleach.clean(self.get_argument("server_path", ""))
import_server_exe = bleach.clean(self.get_argument("server_jar", ""))
server_parts = server.split("|")
captured_roles = []
for role in user_roles:
if bleach.clean(self.get_argument(str(role), '')) == "on":
if bleach.clean(self.get_argument(str(role), "")) == "on":
captured_roles.append(role)
if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!")
return
if import_type == 'import_jar':
good_path = self.controller.verify_jar_server(import_server_path, import_server_exe)
if import_type == "import_jar":
good_path = self.controller.verify_jar_server(
import_server_path, import_server_exe
)
if not good_path:
self.redirect("/panel/error?error=Server path or Server Jar not found!")
self.redirect(
"/panel/error?error=Server path or Server Jar not found!"
)
return
new_server_id = self.controller.import_bedrock_server(server_name, import_server_path,import_server_exe, port)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"imported a jar server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
elif import_type == 'import_zip':
new_server_id = self.controller.import_bedrock_server(
server_name, import_server_path, import_server_exe, port
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f'imported a jar server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
elif import_type == "import_zip":
# here import_server_path means the zip path
zip_path = bleach.clean(self.get_argument('root_path'))
zip_path = bleach.clean(self.get_argument("root_path"))
good_path = helper.check_path_exists(zip_path)
if not good_path:
self.redirect("/panel/error?error=Temp path not found!")
return
new_server_id = self.controller.import_bedrock_zip_server(server_name, zip_path, import_server_exe, port)
new_server_id = self.controller.import_bedrock_zip_server(
server_name, zip_path, import_server_exe, port
)
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with" +
f"sudo chown -R crafty:crafty {import_server_path} And sudo chmod 2775 -R {import_server_path}")
self.redirect(
f"/panel/error?error=Zip file not accessible! "
f"You can fix this permissions issue with"
f"sudo chown -R crafty:crafty {import_server_path} "
f"And sudo chmod 2775 -R {import_server_path}"
)
return
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"imported a zip server named \"{server_name}\"", # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
#deletes temp dir
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f'imported a zip server named "{server_name}"',
new_server_id,
self.get_remote_ip(),
)
# deletes temp dir
file_helper.del_dirs(zip_path)
else:
if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data")
return
server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar
# TODO: add server type check here and call the correct server
# add functions if not a jar
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"created a {server_version} {str(server_type).capitalize()} server named \"{server_name}\"",
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
new_server_id = self.controller.create_jar_server(
server_type, server_version, server_name, min_mem, max_mem, port
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
f"created a {server_version} {str(server_type).capitalize()} "
f'server named "{server_name}"',
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip(),
)
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
# These lines create a new Role for the Server with full permissions
# and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
role_id = self.controller.roles.add_role(f"Creator of Server with uuid={new_server_uuid}")
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
new_server_uuid = self.controller.servers.get_server_data_by_id(
new_server_id
).get("server_uuid")
role_id = self.controller.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}"
)
self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.users.add_role_to_user(
exec_user["user_id"], role_id
)
self.controller.crafty_perms.add_server_creation(
exec_user["user_id"]
)
else:
for role in captured_roles:
role_id = role
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111"
)
self.controller.stats.record_stats()
self.redirect("/panel/dashboard")
@ -369,4 +494,4 @@ class ServerHandler(BaseHandler):
translate=self.translator.translate,
)
except RuntimeError:
self.redirect('/panel/dashboard')
self.redirect("/panel/dashboard")

View File

@ -1,17 +1,24 @@
from typing import ( Optional )
from typing import Optional
try:
import tornado.web
except ModuleNotFoundError as e:
from app.classes.shared.helpers import helper
helper.auto_installer_fix(e)
class CustomStaticHandler(tornado.web.StaticFileHandler):
def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
try:
return super().validate_absolute_path(root, absolute_path)
except tornado.web.HTTPError as error:
if 'HTTP 404: Not Found' in str(error):
if "HTTP 404: Not Found" in str(error):
self.set_status(404)
self.finish({'error':'NOT_FOUND', 'info':'The requested resource was not found on the server'})
self.finish(
{
"error": "NOT_FOUND",
"info": "The requested resource was not found on the server",
}
)

View File

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

View File

@ -34,8 +34,8 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
class Webserver:
class Webserver:
def __init__(self, controller, tasks_manager):
self.ioloop = None
self.HTTP_Server = None
@ -48,12 +48,12 @@ class Webserver:
def log_function(handler):
info = {
'Status_Code': handler.get_status(),
'Method': handler.request.method,
'URL': handler.request.uri,
'Remote_IP': handler.request.remote_ip,
"Status_Code": handler.get_status(),
"Method": handler.request.method,
"URL": handler.request.uri,
"Remote_IP": handler.request.remote_ip,
# pylint: disable=consider-using-f-string
'Elapsed_Time': '%.2fms' % (handler.request.request_time() * 1000)
"Elapsed_Time": "%.2fms" % (handler.request.request_time() * 1000),
}
tornado.log.access_log.info(json.dumps(info, indent=4))
@ -61,23 +61,31 @@ class Webserver:
@staticmethod
def _asyncio_patch():
"""
As of Python 3.8 (on Windows), the asyncio default event handler has changed to "proactor",
As of Python 3.8 (on Windows),
the asyncio default event handler has changed to "proactor",
where tornado expects the "selector" handler.
This function checks if the platform is windows and changes the event handler to suit.
This function checks if the platform is windows and
changes the event handler to suit.
(Taken from https://github.com/mkdocs/mkdocs/commit/cf2b136d4257787c0de51eba2d9e30ded5245b31)
(Taken from
https://github.com/mkdocs/mkdocs/commit/cf2b136d4257787c0de51eba2d9e30ded5245b31)
"""
logger.debug("Checking if asyncio patch is required")
if sys.platform.startswith("win") and sys.version_info >= (3, 8):
# pylint: disable=reimported,import-outside-toplevel,redefined-outer-name
import asyncio
try:
from asyncio import WindowsSelectorEventLoopPolicy
except ImportError:
logger.debug("asyncio patch isn't required") # Can't assign a policy which doesn't exist.
logger.debug(
"asyncio patch isn't required"
) # Can't assign a policy which doesn't exist.
else:
if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):
if not isinstance(
asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy
):
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
logger.debug("Applied asyncio patch")
@ -86,11 +94,11 @@ class Webserver:
# let's verify we have an SSL cert
helper.create_self_signed_cert()
http_port = helper.get_setting('http_port')
https_port = helper.get_setting('https_port')
http_port = helper.get_setting("http_port")
https_port = helper.get_setting("https_port")
debug_errors = helper.get_setting('show_errors')
cookie_secret = helper.get_setting('cookie_secret')
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)
@ -102,38 +110,46 @@ class Webserver:
https_port = 8443
cert_objects = {
'certfile': os.path.join(helper.config_dir, 'web', 'certs', 'commander.cert.pem'),
'keyfile': os.path.join(helper.config_dir, 'web', 'certs', 'commander.key.pem'),
"certfile": os.path.join(
helper.config_dir, "web", "certs", "commander.cert.pem"
),
"keyfile": os.path.join(
helper.config_dir, "web", "certs", "commander.key.pem"
),
}
logger.info(f"Starting Web Server on ports http:{http_port} https:{https_port}")
asyncio.set_event_loop(asyncio.new_event_loop())
tornado.template.Loader('.')
tornado.template.Loader(".")
# TODO: Remove because we don't and won't use
tornado.locale.set_default_locale('en_EN')
tornado.locale.set_default_locale("en_EN")
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager, "translator": translation}
handler_args = {
"controller": self.controller,
"tasks_manager": self.tasks_manager,
"translator": translation,
}
handlers = [
(r'/', DefaultHandler, handler_args),
(r'/public/(.*)', PublicHandler, handler_args),
(r'/panel/(.*)', PanelHandler, handler_args),
(r'/server/(.*)', ServerHandler, handler_args),
(r'/ajax/(.*)', AjaxHandler, handler_args),
(r'/files/(.*)', FileHandler, handler_args),
(r'/api/stats/servers', ServersStats, handler_args),
(r'/api/stats/node', NodeStats, handler_args),
(r'/ws', SocketHandler, handler_args),
(r'/upload', UploadHandler, handler_args),
(r'/status', StatusHandler, handler_args)
]
(r"/", DefaultHandler, handler_args),
(r"/public/(.*)", PublicHandler, handler_args),
(r"/panel/(.*)", PanelHandler, handler_args),
(r"/server/(.*)", ServerHandler, handler_args),
(r"/ajax/(.*)", AjaxHandler, handler_args),
(r"/files/(.*)", FileHandler, handler_args),
(r"/api/stats/servers", ServersStats, handler_args),
(r"/api/stats/node", NodeStats, handler_args),
(r"/ws", SocketHandler, handler_args),
(r"/upload", UploadHandler, handler_args),
(r"/status", StatusHandler, handler_args),
]
app = tornado.web.Application(
handlers,
template_path=os.path.join(helper.webroot, 'templates'),
static_path=os.path.join(helper.webroot, 'static'),
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,
@ -144,25 +160,27 @@ class Webserver:
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)]
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'),
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,
default_handler_class=HTTPHandler,
login_url="/login",
serve_traceback=debug_errors,
)
@ -173,8 +191,14 @@ class Webserver:
self.HTTPS_Server = tornado.httpserver.HTTPServer(app, ssl_options=cert_objects)
self.HTTPS_Server.listen(https_port)
logger.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.")
console.info(f"https://{helper.get_local_ip()}:{https_port} is up and ready for connections.")
logger.info(
f"https://{helper.get_local_ip()}:{https_port} "
f"is up and ready for connections."
)
console.info(
f"https://{helper.get_local_ip()}:{https_port} "
f"is up and ready for connections."
)
console.info("Server Init Complete: Listening For Connections:")

View File

@ -22,11 +22,14 @@ logger = logging.getLogger(__name__)
# Class & Function Defination
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
@tornado.web.stream_request_body
class UploadHandler(BaseHandler):
# noinspection PyAttributeOutsideInit
def initialize(self, controller: 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
@ -35,45 +38,78 @@ class UploadHandler(BaseHandler):
self.do_upload = True
# pylint: disable=unused-variable
api_key, token_data, exec_user = self.current_user
server_id = self.get_argument('server_id', None)
superuser = exec_user['superuser']
server_id = self.get_argument("server_id", None)
superuser = exec_user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
user_id = exec_user['user_id']
user_id = exec_user["user_id"]
if superuser:
exec_user_server_permissions = self.controller.server_perms.list_defined_permissions()
exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
)
elif api_key is not None:
exec_user_server_permissions = self.controller.server_perms.get_api_key_permissions_list(api_key, server_id)
exec_user_server_permissions = (
self.controller.server_perms.get_api_key_permissions_list(
api_key, server_id
)
)
else:
exec_user_server_permissions = self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id)
exec_user_server_permissions = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get('X-ServerId', None)
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')
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')
logger.warning("Server ID not found in upload handler call")
console.warning("Server ID not found in upload handler call")
self.do_upload = False
if Enum_Permissions_Server.Files not in exec_user_server_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
logger.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False
path = self.request.headers.get('X-Path', None)
filename = self.request.headers.get('X-FileName', None)
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(helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path):
print(user_id, server_id, helper.get_os_understandable_path(self.controller.servers.get_server_data_by_id(server_id)['path']), full_path)
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
if not helper.in_path(
helper.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
):
print(
user_id,
server_id,
helper.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
)
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
self.do_upload = False
if self.do_upload:
@ -87,19 +123,19 @@ class UploadHandler(BaseHandler):
def post(self):
logger.info("Upload completed")
files_left = int(self.request.headers.get('X-Files-Left', None))
files_left = int(self.request.headers.get("X-Files-Left", None))
if self.do_upload:
time.sleep(5)
if files_left == 0:
websocket_helper.broadcast('close_upload_box', 'success')
self.finish('success') # Nope, I'm sending "success"
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')
websocket_helper.broadcast("close_upload_box", "error")
self.finish("error")
def data_received(self, chunk):
if self.do_upload:

View File

@ -15,6 +15,7 @@ except ModuleNotFoundError as e:
logger = logging.getLogger(__name__)
class SocketHandler(tornado.websocket.WebSocketHandler):
page = None
page_query_params = None
@ -30,56 +31,71 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
self.io_loop = tornado.ioloop.IOLoop.current()
def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
self.request.remote_ip
remote_ip = (
self.request.headers.get("X-Real-IP")
or self.request.headers.get("X-Forwarded-For")
or self.request.remote_ip
)
return remote_ip
def get_user_id(self):
_, _, user = authentication.check(self.get_cookie('token'))
return user['user_id']
_, _, user = authentication.check(self.get_cookie("token"))
return user["user_id"]
def check_auth(self):
return authentication.check_bool(self.get_cookie('token'))
return authentication.check_bool(self.get_cookie("token"))
# pylint: disable=arguments-differ
def open(self):
logger.debug('Checking WebSocket authentication')
logger.debug("Checking WebSocket authentication")
if self.check_auth():
self.handle()
else:
websocket_helper.send_message(self, 'notification', 'Not authenticated for WebSocket connection')
websocket_helper.send_message(
self, "notification", "Not authenticated for WebSocket connection"
)
self.close()
self.controller.management.add_to_audit_log_raw('unknown',
0, 0,
'Someone tried to connect via WebSocket without proper authentication',
self.get_remote_ip())
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication')
logger.warning('Someone tried to connect via WebSocket without proper authentication')
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):
self.page = self.get_query_argument('page')
self.page_query_params = dict(parse_qsl(helper.remove_prefix(
self.get_query_argument('page_query_params'),
'?'
)))
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')
logger.debug("Opened WebSocket connection")
# pylint: disable=arguments-renamed
@staticmethod
def on_message(raw_message):
logger.debug(f'Got message from WebSocket connection {raw_message}')
logger.debug(f"Got message from WebSocket connection {raw_message}")
message = json.loads(raw_message)
logger.debug(f"Event Type: {message['event']}, Data: {message['data']}")
def on_close(self):
websocket_helper.remove_client(self)
logger.debug('Closed WebSocket connection')
logger.debug("Closed WebSocket connection")
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)
asyncio.run_coroutine_threadsafe(
self.write_message_int(message), self.io_loop.asyncio_loop
)

View File

@ -5,6 +5,7 @@ from app.classes.shared.console import console
logger = logging.getLogger(__name__)
class WebSocketHelper:
def __init__(self):
self.clients = set()
@ -18,16 +19,22 @@ class WebSocketHelper:
# pylint: disable=no-self-use
def send_message(self, client, event_type: str, data):
if client.check_auth():
message = str(json.dumps({'event': event_type, 'data': data}))
message = str(json.dumps({"event": event_type, "data": data}))
client.write_message_helper(message)
def broadcast(self, event_type: str, data):
logger.debug(f"Sending to {len(self.clients)} clients: {json.dumps({'event': event_type, 'data': data})}")
logger.debug(
f"Sending to {len(self.clients)} clients: "
f"{json.dumps({'event': event_type, 'data': data})}"
)
for client in self.clients:
try:
self.send_message(client, event_type, data)
except Exception as e:
logger.exception(f'Error caught while sending WebSocket message to {client.get_remote_ip()} {e}')
logger.exception(
f"Error caught while sending WebSocket message to "
f"{client.get_remote_ip()} {e}"
)
def broadcast_page(self, page: str, event_type: str, data):
def filter_fn(client):
@ -51,7 +58,9 @@ class WebSocketHelper:
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_user_page_params(self, page: str, params: dict, user_id: str, event_type: str, data):
def broadcast_user_page_params(
self, page: str, params: dict, user_id: str, event_type: str, data
):
def filter_fn(client):
if client.get_user_id() != user_id:
return False
@ -77,18 +86,25 @@ class WebSocketHelper:
def broadcast_with_fn(self, filter_fn, event_type: str, data):
clients = list(filter(filter_fn, self.clients))
logger.debug(f"Sending to {len(clients)} out of {len(self.clients)} clients: {json.dumps({'event': event_type, 'data': data})}")
logger.debug(
f"Sending to {len(clients)} out of {len(self.clients)} "
f"clients: {json.dumps({'event': event_type, 'data': data})}"
)
for client in clients:
try:
self.send_message(client, event_type, data)
except Exception as e:
logger.exception(f'Error catched while sending WebSocket message to {client.get_remote_ip()} {e}')
logger.exception(
f"Error catched while sending WebSocket message to "
f"{client.get_remote_ip()} {e}"
)
def disconnect_all(self):
console.info('Disconnecting WebSocket clients')
console.info("Disconnecting WebSocket clients")
for client in self.clients:
client.close()
console.info('Disconnected WebSocket clients')
console.info("Disconnected WebSocket clients")
websocket_helper = WebSocketHelper()

View File

@ -4,6 +4,7 @@ import datetime
def migrate(migrator, database, **kwargs):
db = database
class Users(peewee.Model):
user_id = peewee.AutoField()
created = peewee.DateTimeField(default=datetime.datetime.now)
@ -32,12 +33,12 @@ def migrate(migrator, database, **kwargs):
database = db
class User_Roles(peewee.Model):
user_id = peewee.ForeignKeyField(Users, backref='user_role')
role_id = peewee.ForeignKeyField(Roles, backref='user_role')
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')
table_name = "user_roles"
primary_key = peewee.CompositeKey("user_id", "role_id")
database = db
class Audit_Log(peewee.Model):
@ -45,10 +46,10 @@ def migrate(migrator, database, **kwargs):
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')
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='')
log_msg = peewee.TextField(default="")
class Meta:
database = db
@ -93,27 +94,27 @@ def migrate(migrator, database, **kwargs):
database = db
class User_Servers(peewee.Model):
user_id = peewee.ForeignKeyField(Users, backref='user_server')
server_id = peewee.ForeignKeyField(Servers, backref='user_server')
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')
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')
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')
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)
server_id = peewee.ForeignKeyField(Servers, backref="server", index=True)
started = peewee.CharField(default="")
running = peewee.BooleanField(default=False)
cpu = peewee.FloatField(default=0)
@ -137,10 +138,10 @@ def migrate(migrator, database, **kwargs):
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='')
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:
@ -161,7 +162,7 @@ def migrate(migrator, database, **kwargs):
class Schedules(peewee.Model):
schedule_id = peewee.IntegerField(unique=True, primary_key=True)
server_id = peewee.ForeignKeyField(Servers, backref='schedule_server')
server_id = peewee.ForeignKeyField(Servers, backref="schedule_server")
enabled = peewee.BooleanField()
action = peewee.CharField()
interval = peewee.IntegerField()
@ -171,17 +172,17 @@ def migrate(migrator, database, **kwargs):
comment = peewee.CharField()
class Meta:
table_name = 'schedules'
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')
server_id = peewee.ForeignKeyField(Servers, backref="backups_server")
schedule_id = peewee.ForeignKeyField(Schedules, backref="backups_schedule")
class Meta:
table_name = 'backups'
table_name = "backups"
database = db
migrator.create_table(Backups)
@ -200,16 +201,18 @@ def migrate(migrator, database, **kwargs):
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')
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

@ -1,17 +1,26 @@
# 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
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
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

@ -2,25 +2,27 @@
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')
user_id = ForeignKeyField(Users, backref="users_crafty")
permissions = CharField(default="00000000")
limit_server_creation = IntegerField(default=-1)
class Meta:
table_name = 'user_crafty'
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
def rollback(migrator, database, **kwargs):
migrator.drop_table("user_crafty") # Can be model class OR table name
"""
Write your rollback migrations here.

View File

@ -5,24 +5,25 @@ from app.classes.models.servers import Servers
def migrate(migrator, database, **kwargs):
migrator.drop_table('user_servers') # Can be model class OR table name
migrator.drop_table("user_servers") # Can be model class OR table name
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
db = database
db = database
class User_Servers(Model):
user_id = ForeignKeyField(Users, backref='user_server')
server_id = ForeignKeyField(Servers, backref='user_server')
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')
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

@ -1,24 +1,28 @@
# 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))
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'])
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

@ -2,16 +2,19 @@
import peewee
from app.classes.models.management import Schedules
def migrate(migrator, database, **kwargs):
migrator.drop_columns('backups', ['schedule_id'])
migrator.drop_columns("backups", ["schedule_id"])
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.add_columns('backups', schedule_id=peewee.ForeignKeyField(Schedules, backref='backups_schedule'))
migrator.add_columns(
"backups",
schedule_id=peewee.ForeignKeyField(Schedules, backref="backups_schedule"),
)
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('server_stats', crashed=peewee.BooleanField(default=False))
migrator.add_columns("server_stats", crashed=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('server_stats', ['crashed'])
migrator.drop_columns("server_stats", ["crashed"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('schedules', cron_string=peewee.CharField(default=""))
migrator.add_columns("schedules", cron_string=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['cron_string'])
migrator.drop_columns("schedules", ["cron_string"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('server_stats', first_run=peewee.BooleanField(default=True))
migrator.add_columns("server_stats", first_run=peewee.BooleanField(default=True))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('server_stats', ['first_run'])
migrator.drop_columns("server_stats", ["first_run"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('schedules', one_time=peewee.BooleanField(default=False))
migrator.add_columns("schedules", one_time=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['one_time'])
migrator.drop_columns("schedules", ["one_time"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('servers', type=peewee.CharField(default="minecraft-java"))
migrator.add_columns("servers", type=peewee.CharField(default="minecraft-java"))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('servers', ['type'])
migrator.drop_columns("servers", ["type"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', email=peewee.CharField(default="default@example.com"))
migrator.add_columns("users", email=peewee.CharField(default="default@example.com"))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['email'])
migrator.drop_columns("users", ["email"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', support_logs=peewee.CharField(default=""))
migrator.add_columns("users", support_logs=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['support_logs'])
migrator.drop_columns("users", ["support_logs"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('server_stats', waiting_start=peewee.BooleanField(default=False))
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'])
migrator.drop_columns("server_stats", ["waiting_start"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', lang=peewee.CharField(default='en_EN'))
migrator.add_columns("users", lang=peewee.CharField(default="en_EN"))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['lang'])
migrator.drop_columns("users", ["lang"])
"""
Write your rollback migrations here.
"""

View File

@ -3,10 +3,14 @@ import datetime
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now))
migrator.drop_columns('users', ['api_token'])
migrator.add_columns(
"users", valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now)
)
migrator.drop_columns("users", ["api_token"])
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['valid_tokens_from'])
migrator.add_columns('users', api_token=peewee.CharField(default="", unique=True, index=True))
migrator.drop_columns("users", ["valid_tokens_from"])
migrator.add_columns(
"users", api_token=peewee.CharField(default="", unique=True, index=True)
)

View File

@ -6,18 +6,18 @@ from app.classes.models.users import Users
def migrate(migrator, db):
class ApiKeys(peewee.Model):
token_id = peewee.AutoField()
name = peewee.CharField(default='', unique=True, index=True)
name = peewee.CharField(default="", unique=True, index=True)
created = peewee.DateTimeField(default=datetime.datetime.now)
user = peewee.ForeignKeyField(Users, backref='api_token', index=True)
server_permissions = peewee.CharField(default='00000000')
crafty_permissions = peewee.CharField(default='000')
user = peewee.ForeignKeyField(Users, backref="api_token", index=True)
server_permissions = peewee.CharField(default="00000000")
crafty_permissions = peewee.CharField(default="000")
superuser = peewee.BooleanField(default=False)
class Meta:
table_name = 'api_keys'
table_name = "api_keys"
migrator.create_table(ApiKeys)
def rollback(migrator, db):
migrator.drop_table('api_keys')
migrator.drop_table("api_keys")

View File

@ -1,18 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('schedules', parent=peewee.IntegerField(null=True))
migrator.add_columns('schedules', delay=peewee.IntegerField(default=0))
migrator.add_columns("schedules", parent=peewee.IntegerField(null=True))
migrator.add_columns("schedules", delay=peewee.IntegerField(default=0))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('schedules', ['parent'])
migrator.drop_columns('schedules', ['delay'])
migrator.drop_columns("schedules", ["parent"])
migrator.drop_columns("schedules", ["delay"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', server_order=peewee.CharField(default=''))
migrator.add_columns("users", server_order=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['server_order'])
migrator.drop_columns("users", ["server_order"])
"""
Write your rollback migrations here.
"""

View File

@ -1,8 +1,10 @@
# Generated by database migrator
import peewee
def migrate(migrator, db):
migrator.rename_column('backups', 'directories', 'excluded_dirs')
migrator.rename_column("backups", "directories", "excluded_dirs")
def rollback(migrator, db):
migrator.rename_column('backups', 'excluded_dirs', 'directories')
migrator.rename_column("backups", "excluded_dirs", "directories")

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('backups', compress=peewee.BooleanField(default=False))
migrator.add_columns("backups", compress=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('backups', ['compress'])
migrator.drop_columns("backups", ["compress"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('server_stats', downloading=peewee.BooleanField(default=False))
migrator.add_columns("server_stats", downloading=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('server_stats', ['downloading'])
migrator.drop_columns("server_stats", ["downloading"])
"""
Write your rollback migrations here.
"""

View File

@ -1,16 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', preparing=peewee.BooleanField(default=False))
migrator.add_columns("users", preparing=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['preparing'])
migrator.drop_columns("users", ["preparing"])
"""
Write your rollback migrations here.
"""

View File

@ -1,3 +1,4 @@
---
version: '3'
services:
@ -7,11 +8,11 @@ services:
environment:
- TZ=Etc/UTC
ports:
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "25500-25600:25500-25600" # MC SERV PORT RANGE
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "25500-25600:25500-25600" # MC SERV PORT RANGE
volumes:
- ./docker/backups:/commander/backups
- ./docker/logs:/commander/logs

View File

@ -1,3 +1,4 @@
---
version: '3'
services:
@ -7,11 +8,11 @@ services:
environment:
- TZ=Etc/UTC
ports:
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "25500-25600:25500-25600" # MC SERV PORT RANGE
- "8000:8000" # HTTP
- "8443:8443" # HTTPS
- "8123:8123" # DYNMAP
- "19132:19132/udp" # BEDROCK
- "25500-25600:25500-25600" # MC SERV PORT RANGE
volumes:
- ./backups:/commander/backups
- ./logs:/commander/logs

80
main.py
View File

@ -7,8 +7,12 @@ import logging.config
import signal
from app.classes.shared.console import console
from app.classes.shared.helpers import helper
if helper.checkRoot():
console.critical("Root detected. Root/Admin access denied. Run Crafty again with non-elevated permissions.")
console.critical(
"Root detected. Root/Admin access denied. "
"Run Crafty again with non-elevated permissions."
)
time.sleep(5)
console.critical("Crafty shutting down. Root/Admin access denied.")
sys.exit(0)
@ -39,18 +43,14 @@ def do_intro():
def setup_logging(debug=True):
logging_config_file = os.path.join(os.path.curdir,
'app',
'config',
'logging.json'
)
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
if os.path.exists(logging_config_file):
# open our logging config file
with open(logging_config_file, 'rt', encoding='utf-8') as f:
with open(logging_config_file, "rt", encoding="utf-8") as f:
logging_config = json.load(f)
if debug:
logging_config['loggers']['']['level'] = 'DEBUG'
logging_config["loggers"][""]["level"] = "DEBUG"
logging.config.dictConfig(logging_config)
@ -61,23 +61,23 @@ def setup_logging(debug=True):
# Our Main Starter
if __name__ == '__main__':
if __name__ == "__main__":
parser = argparse.ArgumentParser("Crafty Controller - A Server Management System")
parser.add_argument('-i', '--ignore',
action='store_true',
help="Ignore session.lock files"
)
parser.add_argument(
"-i", "--ignore", action="store_true", help="Ignore session.lock files"
)
parser.add_argument('-v', '--verbose',
action='store_true',
help="Sets logging level to debug."
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Sets logging level to debug."
)
parser.add_argument('-d', '--daemon',
action='store_true',
help="Runs Crafty in daemon mode (no prompt)"
)
parser.add_argument(
"-d",
"--daemon",
action="store_true",
help="Runs Crafty in daemon mode (no prompt)",
)
args = parser.parse_args()
@ -95,17 +95,20 @@ if __name__ == '__main__':
# our session file, helps prevent multiple controller agents on the same machine.
helper.create_session_file(ignore=args.ignore)
migration_manager = MigrationManager(database)
migration_manager.up() # Automatically runs migrations
migration_manager.up() # Automatically runs migrations
# do our installer stuff
fresh_install = installer.is_fresh_install()
if fresh_install:
console.debug("Fresh install detected")
console.warning("We have detected a fresh install. Please be sure to forward Crafty's port, " +
f"{helper.get_setting('https_port')}, through your router/firewall if you would like to be able to access Crafty remotely.")
console.warning(
f"We have detected a fresh install. Please be sure to forward "
f"Crafty's port, {helper.get_setting('https_port')}, "
f"through your router/firewall if you would like to be able "
f"to access Crafty remotely."
)
installer.default_settings()
else:
console.debug("Existing install detected")
@ -116,7 +119,7 @@ if __name__ == '__main__':
tasks_manager.start_webserver()
# slowing down reporting just for a 1/2 second so messages look cleaner
time.sleep(.5)
time.sleep(0.5)
# init servers
logger.info("Initializing all servers defined")
@ -127,18 +130,23 @@ if __name__ == '__main__':
# start stats logging
tasks_manager.start_stats_recording()
# once the controller is up and stats are logging, we can kick off the scheduler officially
# once the controller is up and stats are logging, we can kick off
# the scheduler officially
tasks_manager.start_scheduler()
# refresh our cache and schedule for every 12 hoursour cache refresh for serverjars.com
# refresh our cache and schedule for every 12 hoursour cache refresh
# for serverjars.com
tasks_manager.serverjar_cache_refresher()
logger.info("Checking Internet. This may take a minute.")
console.info("Checking Internet. This may take a minute.")
if not helper.check_internet():
console.warning("We have detected the machine running Crafty has no connection to the internet. " +
"Client connections to the server may be limited.")
console.warning(
"We have detected the machine running Crafty has no "
"connection to the internet. Client connections to "
"the server may be limited."
)
if not controller.check_system_user():
controller.add_system_user()
@ -151,9 +159,13 @@ if __name__ == '__main__':
controller.clear_support_status()
def sigterm_handler(*sig):
print() # for newline
logger.info(f"Recieved {signal.Signals(sig[0]).name} [{sig[0]}], stopping Crafty...")
console.info(f"Recieved {signal.Signals(sig[0]).name} [{sig[0]}], stopping Crafty...")
print() # for newline
logger.info(
f"Recieved {signal.Signals(sig[0]).name} [{sig[0]}], stopping Crafty..."
)
console.info(
f"Recieved {signal.Signals(sig[0]).name} [{sig[0]}], stopping Crafty..."
)
tasks_manager._main_graceful_exit()
Crafty.universal_exit()
@ -163,7 +175,7 @@ if __name__ == '__main__':
try:
Crafty.cmdloop()
except KeyboardInterrupt:
print() # for newline
print() # for newline
logger.info("Recieved SIGINT, stopping Crafty...")
console.info("Recieved SIGINT, stopping Crafty...")
tasks_manager._main_graceful_exit()