Merge branch 'dev' into 'master'

4.0.11

See merge request crafty-controller/crafty-4!439
This commit is contained in:
Iain Powrie 2022-08-28 00:13:41 +00:00
commit 5af3c30aa5
45 changed files with 1139 additions and 270 deletions

View File

@ -1,4 +1,19 @@
# Changelog # Changelog
## --- [4.0.11] - 2022/08/28
### New features
- Add server import status indicators ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/433))
- Users can now be assigned as manager of other users/roles ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434))
- Add variable shutdown timeouts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/435))
- Add server metrics graph ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/436))
### Bug fixes
- Fix creation quota not refilling after server delete ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434))
- Add missing bedrock dependency (libcurl.so.4) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/437))
### Tweaks
- Make imports threaded ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/433))
- Add 'Created By' Field to servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434))
- Add Zip comments to support archives ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/438))
<br><br>
## --- [4.0.10] - 2022/08/14 ## --- [4.0.10] - 2022/08/14
### Bug fixes ### Bug fixes
- Fix reaction tasks not firing ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/423)) - Fix reaction tasks not firing ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/423))

View File

@ -15,6 +15,7 @@ RUN apt-get update \
&& apt-get -y --no-install-recommends install \ && apt-get -y --no-install-recommends install \
sudo \ sudo \
gcc \ gcc \
libcurl4 \
python3 \ python3 \
python3-dev \ python3-dev \
python3-pip \ python3-pip \

View File

@ -2,11 +2,11 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org) [![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.10--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases) [![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.11--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4) [![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master) [![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
# Crafty Controller 4.0.10-beta # Crafty Controller 4.0.11-beta
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?

View File

@ -60,26 +60,6 @@ class CraftyPermsController:
permissions_list = PermissionsCrafty.get_permissions(permissions_mask) permissions_list = PermissionsCrafty.get_permissions(permissions_mask)
return permissions_list return permissions_list
@staticmethod
def add_server_creation(user_id):
"""Increase the "Server Creation" counter for this user
Args:
user_id (int): The modifiable user's ID
Returns:
int: The new count of servers created by this user
"""
return PermissionsCrafty.add_server_creation(user_id)
@staticmethod
def add_user_creation(user_id):
return PermissionsCrafty.add_user_creation(user_id)
@staticmethod
def add_role_creation(user_id):
return PermissionsCrafty.add_role_creation(user_id)
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys): def get_api_key_permissions_list(key: ApiKeys):
return PermissionsCrafty.get_api_key_permissions_list(key) return PermissionsCrafty.get_api_key_permissions_list(key)

View File

@ -64,8 +64,8 @@ class RolesController:
HelperRoles.update_role(role_id, up_data) HelperRoles.update_role(role_id, up_data)
@staticmethod @staticmethod
def add_role(role_name): def add_role(role_name, manager):
return HelperRoles.add_role(role_name) return HelperRoles.add_role(role_name, manager)
class RoleServerJsonType(t.TypedDict): class RoleServerJsonType(t.TypedDict):
server_id: t.Union[str, int] server_id: t.Union[str, int]
@ -92,6 +92,7 @@ class RolesController:
def add_role_advanced( def add_role_advanced(
name: str, name: str,
servers: t.Iterable[RoleServerJsonType], servers: t.Iterable[RoleServerJsonType],
manager: int,
) -> int: ) -> int:
"""Add a role with a name and a list of servers """Add a role with a name and a list of servers
@ -102,7 +103,7 @@ class RolesController:
Returns: Returns:
int: The new role's ID int: The new role's ID
""" """
role_id: t.Final[int] = HelperRoles.add_role(name) role_id: t.Final[int] = HelperRoles.add_role(name, manager)
for server in servers: for server in servers:
PermissionsServers.get_or_create( PermissionsServers.get_or_create(
role_id, server["server_id"], server["permissions"] role_id, server["server_id"], server["permissions"]
@ -114,6 +115,7 @@ class RolesController:
role_id: t.Union[str, int], role_id: t.Union[str, int],
role_name: t.Optional[str], role_name: t.Optional[str],
servers: t.Optional[t.Iterable[RoleServerJsonType]], servers: t.Optional[t.Iterable[RoleServerJsonType]],
manager: int,
) -> None: ) -> None:
"""Update a role with a name and a list of servers """Update a role with a name and a list of servers
@ -152,6 +154,7 @@ class RolesController:
up_data = { up_data = {
"role_name": role_name, "role_name": role_name,
"last_update": Helpers.get_time_as_string(), "last_update": Helpers.get_time_as_string(),
"manager": manager,
} }
# TODO: do the last_update on the db side # TODO: do the last_update on the db side
HelperRoles.update_role(role_id, up_data) HelperRoles.update_role(role_id, up_data)

View File

@ -4,6 +4,7 @@ import time
import json import json
import pathlib import pathlib
import typing as t import typing as t
import datetime
from app.classes.controllers.roles_controller import RolesController from app.classes.controllers.roles_controller import RolesController
from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.file_helpers import FileHelpers
@ -51,6 +52,7 @@ class ServersController(metaclass=Singleton):
server_log_file: str, server_log_file: str,
server_stop: str, server_stop: str,
server_type: str, server_type: str,
created_by: int,
server_port: int = 25565, server_port: int = 25565,
server_host: str = "127.0.0.1", server_host: str = "127.0.0.1",
) -> int: ) -> int:
@ -85,6 +87,7 @@ class ServersController(metaclass=Singleton):
server_log_file, server_log_file,
server_stop, server_stop,
server_type, server_type,
created_by,
server_port, server_port,
server_host, server_host,
) )
@ -102,25 +105,32 @@ class ServersController(metaclass=Singleton):
server_instance.update_server_instance() server_instance.update_server_instance()
return ret return ret
def get_history_stats(self, server_id):
max_age = self.helper.get_setting("history_max_age")
now = datetime.datetime.now()
minimum_to_exist = now - datetime.timedelta(days=max_age)
srv = ServersController().get_server_instance_by_id(server_id)
return srv.stats_helper.get_history_stats(server_id, minimum_to_exist)
@staticmethod @staticmethod
def update_unloaded_server(server_obj): def update_unloaded_server(server_obj):
ret = HelperServers.update_server(server_obj) ret = HelperServers.update_server(server_obj)
return ret return ret
@staticmethod @staticmethod
def set_download(server_id): def set_import(server_id):
srv = ServersController().get_server_instance_by_id(server_id) srv = ServersController().get_server_instance_by_id(server_id)
return srv.stats_helper.set_download() return srv.stats_helper.set_import()
@staticmethod @staticmethod
def finish_download(server_id): def finish_import(server_id):
srv = ServersController().get_server_instance_by_id(server_id) srv = ServersController().get_server_instance_by_id(server_id)
return srv.stats_helper.finish_download() return srv.stats_helper.finish_import()
@staticmethod @staticmethod
def get_download_status(server_id): def get_import_status(server_id):
server = ServersController().get_server_instance_by_id(server_id) server = ServersController().get_server_instance_by_id(server_id)
return server.stats_helper.get_download_status() return server.stats_helper.get_import_status()
def remove_server(self, server_id): def remove_server(self, server_id):
roles_list = PermissionsServers.get_roles_from_server(server_id) roles_list = PermissionsServers.get_roles_from_server(server_id)

View File

@ -1,7 +1,9 @@
import logging import logging
import typing as t import typing as t
from app.classes.models.servers import HelperServers
from app.classes.models.users import HelperUsers from app.classes.models.users import HelperUsers
from app.classes.models.roles import HelperRoles
from app.classes.models.crafty_permissions import ( from app.classes.models.crafty_permissions import (
PermissionsCrafty, PermissionsCrafty,
EnumPermissionsCrafty, EnumPermissionsCrafty,
@ -132,6 +134,18 @@ class UsersController:
def set_support_path(user_id, support_path): def set_support_path(user_id, support_path):
HelperUsers.set_support_path(user_id, support_path) HelperUsers.set_support_path(user_id, support_path)
@staticmethod
def get_managed_users(exec_user_id):
return HelperUsers.get_managed_users(exec_user_id)
@staticmethod
def get_managed_roles(exec_user_id):
return HelperUsers.get_managed_roles(exec_user_id)
@staticmethod
def get_created_servers(exec_user_id):
return HelperServers.get_total_owned_servers(exec_user_id)
def update_user(self, user_id: str, user_data=None, user_crafty_data=None): def update_user(self, user_id: str, user_data=None, user_crafty_data=None):
if user_crafty_data is None: if user_crafty_data is None:
user_crafty_data = {} user_crafty_data = {}
@ -206,6 +220,7 @@ class UsersController:
def add_user( def add_user(
self, self,
username, username,
manager,
password, password,
email="default@example.com", email="default@example.com",
enabled: bool = True, enabled: bool = True,
@ -213,6 +228,7 @@ class UsersController:
): ):
return self.users_helper.add_user( return self.users_helper.add_user(
username, username,
manager,
password=password, password=password,
email=email, email=email,
enabled=enabled, enabled=enabled,
@ -236,6 +252,10 @@ class UsersController:
) )
def remove_user(self, user_id): def remove_user(self, user_id):
for user in self.get_managed_users(user_id):
self.update_user(user.user_id, {"manager": None})
for role in HelperUsers.get_managed_roles(user_id):
HelperRoles.update_role(role.role_id, {"manager": None})
return self.users_helper.remove_user(user_id) return self.users_helper.remove_user(user_id)
@staticmethod @staticmethod

View File

@ -175,7 +175,7 @@ class ServerJars:
# we submit a db update for it's stats. # we submit a db update for it's stats.
while True: while True:
try: try:
ServersController.set_download(server_id) ServersController.set_import(server_id)
for user in server_users: for user in server_users:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {} user, "send_start_reload", {}
@ -190,7 +190,7 @@ class ServerJars:
try: try:
with open(path, "wb") as output: with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output) shutil.copyfileobj(r.raw, output)
ServersController.finish_download(server_id) ServersController.finish_import(server_id)
for user in server_users: for user in server_users:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
@ -203,7 +203,7 @@ class ServerJars:
return True return True
except Exception as e: except Exception as e:
logger.error(f"Unable to save jar to {path} due to error:{e}") logger.error(f"Unable to save jar to {path} due to error:{e}")
ServersController.finish_download(server_id) ServersController.finish_import(server_id)
server_users = PermissionsServers.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users: for user in server_users:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(

View File

@ -9,6 +9,7 @@ from peewee import (
) )
from app.classes.models.base_model import BaseModel from app.classes.models.base_model import BaseModel
from app.classes.models.servers import HelperServers
from app.classes.models.users import Users, ApiKeys, HelperUsers from app.classes.models.users import Users, ApiKeys, HelperUsers
from app.classes.shared.permission_helper import PermissionHelper from app.classes.shared.permission_helper import PermissionHelper
@ -23,9 +24,6 @@ class UserCrafty(BaseModel):
limit_server_creation = IntegerField(default=-1) limit_server_creation = IntegerField(default=-1)
limit_user_creation = IntegerField(default=0) limit_user_creation = IntegerField(default=0)
limit_role_creation = IntegerField(default=0) limit_role_creation = IntegerField(default=0)
created_server = IntegerField(default=0)
created_user = IntegerField(default=0)
created_role = IntegerField(default=0)
class Meta: class Meta:
table_name = "user_crafty" table_name = "user_crafty"
@ -107,9 +105,6 @@ class PermissionsCrafty:
UserCrafty.limit_server_creation: 0, UserCrafty.limit_server_creation: 0,
UserCrafty.limit_user_creation: 0, UserCrafty.limit_user_creation: 0,
UserCrafty.limit_role_creation: 0, UserCrafty.limit_role_creation: 0,
UserCrafty.created_server: 0,
UserCrafty.created_user: 0,
UserCrafty.created_role: 0,
} }
).execute() ).execute()
user_crafty = PermissionsCrafty.get_user_crafty(user_id) user_crafty = PermissionsCrafty.get_user_crafty(user_id)
@ -159,11 +154,16 @@ class PermissionsCrafty:
@staticmethod @staticmethod
def get_created_quantity_list(user_id): def get_created_quantity_list(user_id):
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
quantity_list = { quantity_list = {
EnumPermissionsCrafty.SERVER_CREATION.name: user_crafty.created_server, EnumPermissionsCrafty.SERVER_CREATION.name: HelperServers.get_total_owned_servers( # pylint: disable=line-too-long
EnumPermissionsCrafty.USER_CONFIG.name: user_crafty.created_user, user_id
EnumPermissionsCrafty.ROLES_CONFIG.name: user_crafty.created_role, ),
EnumPermissionsCrafty.USER_CONFIG.name: HelperUsers.get_managed_users(
user_id
).count(),
EnumPermissionsCrafty.ROLES_CONFIG.name: HelperUsers.get_managed_roles(
user_id
).count(),
} }
return quantity_list return quantity_list
@ -183,31 +183,6 @@ class PermissionsCrafty:
or limit_list[permission.name] == -1 or limit_list[permission.name] == -1
) )
@staticmethod
def add_server_creation(user_id: int):
"""Increase the "Server Creation" counter for this user
Args:
user_id (int): The modifiable user's ID
"""
UserCrafty.update(created_server=UserCrafty.created_server + 1).where(
UserCrafty.user_id == user_id
).execute()
@staticmethod
def add_user_creation(user_id):
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
user_crafty.created_user += 1
UserCrafty.save(user_crafty)
return user_crafty.created_user
@staticmethod
def add_role_creation(user_id):
user_crafty = PermissionsCrafty.get_user_crafty(user_id)
user_crafty.created_role += 1
UserCrafty.save(user_crafty)
return user_crafty.created_role
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys): def get_api_key_permissions_list(key: ApiKeys):
user = HelperUsers.get_user(key.user_id) user = HelperUsers.get_user(key.user_id)

View File

@ -6,6 +6,7 @@ from peewee import (
DoesNotExist, DoesNotExist,
AutoField, AutoField,
DateTimeField, DateTimeField,
IntegerField,
) )
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
@ -22,6 +23,7 @@ class Roles(BaseModel):
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(default=datetime.datetime.now) last_update = DateTimeField(default=datetime.datetime.now)
role_name = CharField(default="", unique=True, index=True) role_name = CharField(default="", unique=True, index=True)
manager = IntegerField(null=True)
class Meta: class Meta:
table_name = "roles" table_name = "roles"
@ -71,11 +73,12 @@ class HelperRoles:
) )
@staticmethod @staticmethod
def add_role(role_name): def add_role(role_name, manager):
role_id = Roles.insert( role_id = Roles.insert(
{ {
Roles.role_name: role_name.lower(), Roles.role_name: role_name.lower(),
Roles.created: Helpers.get_time_as_string(), Roles.created: Helpers.get_time_as_string(),
Roles.manager: manager,
} }
).execute() ).execute()
return role_id return role_id

View File

@ -53,7 +53,7 @@ class ServerStats(Model):
waiting_start = BooleanField(default=False) waiting_start = BooleanField(default=False)
first_run = BooleanField(default=True) first_run = BooleanField(default=True)
crashed = BooleanField(default=False) crashed = BooleanField(default=False)
downloading = BooleanField(default=False) importing = BooleanField(default=False)
class Meta: class Meta:
table_name = "server_stats" table_name = "server_stats"
@ -137,6 +137,14 @@ class HelperServerStats:
) )
return server_data return server_data
def get_history_stats(self, server_id, max_age):
return (
ServerStats.select()
.where(ServerStats.created > max_age)
.where(ServerStats.server_id == server_id)
.execute(self.database)
)
def insert_server_stats(self, server_stats): def insert_server_stats(self, server_stats):
server_id = server_stats.get("id", 0) server_id = server_stats.get("id", 0)
@ -207,26 +215,26 @@ class HelperServerStats:
ServerStats.server_id == self.server_id ServerStats.server_id == self.server_id
).execute(self.database) ).execute(self.database)
def set_download(self): def set_import(self):
# self.select_database(self.server_id) # self.select_database(self.server_id)
ServerStats.update(downloading=True).where( ServerStats.update(importing=True).where(
ServerStats.server_id == self.server_id ServerStats.server_id == self.server_id
).execute(self.database) ).execute(self.database)
def finish_download(self): def finish_import(self):
# self.select_database(self.server_id) # self.select_database(self.server_id)
ServerStats.update(downloading=False).where( ServerStats.update(importing=False).where(
ServerStats.server_id == self.server_id ServerStats.server_id == self.server_id
).execute(self.database) ).execute(self.database)
def get_download_status(self): def get_import_status(self):
# self.select_database(self.server_id) # self.select_database(self.server_id)
download_status = ( import_status = (
ServerStats.select() ServerStats.select()
.where(ServerStats.server_id == self.server_id) .where(ServerStats.server_id == self.server_id)
.get(self.database) .get(self.database)
) )
return download_status.downloading return import_status.importing
def server_crash_reset(self): def server_crash_reset(self):
if self.server_id is None: if self.server_id is None:
@ -249,7 +257,6 @@ class HelperServerStats:
def set_update(self, value): def set_update(self, value):
if self.server_id is None: if self.server_id is None:
return return
# self.select_database(self.server_id) # self.select_database(self.server_id)
try: try:
# Checks if server even exists # Checks if server even exists

View File

@ -38,6 +38,8 @@ class Servers(BaseModel):
logs_delete_after = IntegerField(default=0) logs_delete_after = IntegerField(default=0)
type = CharField(default="minecraft-java") type = CharField(default="minecraft-java")
show_status = BooleanField(default=1) show_status = BooleanField(default=1)
created_by = IntegerField(default=-100)
shutdown_timeout = IntegerField(default=60)
class Meta: class Meta:
table_name = "servers" table_name = "servers"
@ -64,6 +66,7 @@ class HelperServers:
server_log_file: str, server_log_file: str,
server_stop: str, server_stop: str,
server_type: str, server_type: str,
created_by: int,
server_port: int = 25565, server_port: int = 25565,
server_host: str = "127.0.0.1", server_host: str = "127.0.0.1",
) -> int: ) -> int:
@ -105,6 +108,7 @@ class HelperServers:
Servers.stop_command: server_stop, Servers.stop_command: server_stop,
Servers.backup_path: backup_path, Servers.backup_path: backup_path,
Servers.type: server_type, Servers.type: server_type,
Servers.created_by: created_by,
} }
).execute() ).execute()
@ -112,6 +116,10 @@ class HelperServers:
def get_server_obj(server_id): def get_server_obj(server_id):
return Servers.get_by_id(server_id) return Servers.get_by_id(server_id)
@staticmethod
def get_total_owned_servers(user_id):
return Servers.select().where(Servers.created_by == user_id).count()
@staticmethod @staticmethod
def get_server_type_by_id(server_id): def get_server_type_by_id(server_id):
server_type = Servers.select().where(Servers.server_id == server_id).get() server_type = Servers.select().where(Servers.server_id == server_id).get()

View File

@ -6,6 +6,7 @@ from peewee import (
ForeignKeyField, ForeignKeyField,
CharField, CharField,
AutoField, AutoField,
IntegerField,
DateTimeField, DateTimeField,
BooleanField, BooleanField,
CompositeKey, CompositeKey,
@ -40,6 +41,7 @@ class Users(BaseModel):
server_order = CharField(default="") server_order = CharField(default="")
preparing = BooleanField(default=False) preparing = BooleanField(default=False)
hints = BooleanField(default=True) hints = BooleanField(default=True)
manager = IntegerField(default=None, null=True)
class Meta: class Meta:
table_name = "users" table_name = "users"
@ -138,6 +140,16 @@ class HelperUsers:
user_query = Users.select().where(Users.user_id == user_id) user_query = Users.select().where(Users.user_id == user_id)
return user_query return user_query
@staticmethod
def get_managed_users(exec_user_id):
user_query = Users.select().where(Users.manager == exec_user_id)
return user_query
@staticmethod
def get_managed_roles(exec_user_id):
roles_query = Roles.select().where(Roles.manager == exec_user_id)
return roles_query
@staticmethod @staticmethod
def get_user(user_id): def get_user(user_id):
if user_id == 0: if user_id == 0:
@ -192,6 +204,7 @@ class HelperUsers:
def add_user( def add_user(
self, self,
username: str, username: str,
manager: str,
password: str = None, password: str = None,
email: t.Optional[str] = None, email: t.Optional[str] = None,
enabled: bool = True, enabled: bool = True,
@ -209,6 +222,7 @@ class HelperUsers:
Users.enabled: enabled, Users.enabled: enabled,
Users.superuser: superuser, Users.superuser: superuser,
Users.created: Helpers.get_time_as_string(), Users.created: Helpers.get_time_as_string(),
Users.manager: manager,
} }
).execute() ).execute()
return user_id return user_id
@ -229,6 +243,7 @@ class HelperUsers:
Users.enabled: enabled, Users.enabled: enabled,
Users.superuser: superuser, Users.superuser: superuser,
Users.created: Helpers.get_time_as_string(), Users.created: Helpers.get_time_as_string(),
Users.manager: None,
} }
).execute() ).execute()
return user_id return user_id

View File

@ -27,10 +27,15 @@ class FileHelpers:
FileHelpers.del_dirs(sub) FileHelpers.del_dirs(sub)
else: else:
# Delete file if it is a file: # Delete file if it is a file:
sub.unlink() try:
sub.unlink()
# This removes the top-level folder: except:
path.rmdir() logger.error(f"Unable to delete file {sub}")
try:
# This removes the top-level folder:
path.rmdir()
except:
logger.error("Unable to remove top level")
return True return True
@staticmethod @staticmethod
@ -65,10 +70,13 @@ class FileHelpers:
FileHelpers.del_file(src_path) FileHelpers.del_file(src_path)
@staticmethod @staticmethod
def make_archive(path_to_destination, path_to_zip): def make_archive(path_to_destination, path_to_zip, comment=""):
# create a ZipFile object # create a ZipFile object
path_to_destination += ".zip" path_to_destination += ".zip"
with ZipFile(path_to_destination, "w") as zip_file: with ZipFile(path_to_destination, "w") as zip_file:
zip_file.comment = bytes(
comment, "utf-8"
) # comments over 65535 bytes will be truncated
for root, _dirs, files in os.walk(path_to_zip, topdown=True): for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip ziproot = path_to_zip
for file in files: for file in files:
@ -93,10 +101,13 @@ class FileHelpers:
return True return True
@staticmethod @staticmethod
def make_compressed_archive(path_to_destination, path_to_zip): def make_compressed_archive(path_to_destination, path_to_zip, comment=""):
# create a ZipFile object # create a ZipFile object
path_to_destination += ".zip" path_to_destination += ".zip"
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file: with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
zip_file.comment = bytes(
comment, "utf-8"
) # comments over 65535 bytes will be truncated
for root, _dirs, files in os.walk(path_to_zip, topdown=True): for root, _dirs, files in os.walk(path_to_zip, topdown=True):
ziproot = path_to_zip ziproot = path_to_zip
for file in files: for file in files:
@ -122,7 +133,7 @@ class FileHelpers:
return True return True
def make_compressed_backup( def make_compressed_backup(
self, path_to_destination, path_to_zip, excluded_dirs, server_id self, path_to_destination, path_to_zip, excluded_dirs, server_id, comment=""
): ):
# create a ZipFile object # create a ZipFile object
path_to_destination += ".zip" path_to_destination += ".zip"
@ -140,6 +151,9 @@ class FileHelpers:
results, results,
) )
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file: with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
zip_file.comment = bytes(
comment, "utf-8"
) # comments over 65535 bytes will be truncated
for root, dirs, files in os.walk(path_to_zip, topdown=True): for root, dirs, files in os.walk(path_to_zip, topdown=True):
for l_dir in dirs: for l_dir in dirs:
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace: if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
@ -184,7 +198,9 @@ class FileHelpers:
return True return True
def make_backup(self, path_to_destination, path_to_zip, excluded_dirs, server_id): def make_backup(
self, path_to_destination, path_to_zip, excluded_dirs, server_id, comment=""
):
# create a ZipFile object # create a ZipFile object
path_to_destination += ".zip" path_to_destination += ".zip"
ex_replace = [p.replace("\\", "/") for p in excluded_dirs] ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
@ -201,6 +217,9 @@ class FileHelpers:
results, results,
) )
with ZipFile(path_to_destination, "w") as zip_file: with ZipFile(path_to_destination, "w") as zip_file:
zip_file.comment = bytes(
comment, "utf-8"
) # comments over 65535 bytes will be truncated
for root, dirs, files in os.walk(path_to_zip, topdown=True): for root, dirs, files in os.walk(path_to_zip, topdown=True):
for l_dir in dirs: for l_dir in dirs:
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace: if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:

View File

@ -0,0 +1,216 @@
import os
import time
import shutil
import logging
import threading
from app.classes.controllers.server_perms_controller import PermissionsServers
from app.classes.controllers.servers_controller import ServersController
from app.classes.shared.helpers import Helpers
from app.classes.shared.file_helpers import FileHelpers
logger = logging.getLogger(__name__)
class ImportHelpers:
allowed_quotes = ['"', "'", "`"]
def __init__(self, helper, file_helper):
self.file_helper: FileHelpers = file_helper
self.helper: Helpers = helper
def import_jar_server(self, server_path, new_server_dir, port, new_id):
import_thread = threading.Thread(
target=self.import_threaded_jar_server,
daemon=True,
args=(server_path, new_server_dir, port, new_id),
name=f"{new_id}_import",
)
import_thread.start()
def import_threaded_jar_server(self, server_path, new_server_dir, port, new_id):
for item in os.listdir(server_path):
if not item == "db_stats":
try:
if os.path.isdir(os.path.join(server_path, item)):
FileHelpers.copy_dir(
os.path.join(server_path, item),
os.path.join(new_server_dir, item),
)
else:
FileHelpers.copy_file(
os.path.join(server_path, item),
os.path.join(new_server_dir, item),
)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
time.sleep(5)
ServersController.finish_import(new_id)
server_users = PermissionsServers.get_server_user_list(new_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {})
def import_java_zip_server(self, temp_dir, new_server_dir, port, new_id):
import_thread = threading.Thread(
target=self.import_threaded_java_zip_server,
daemon=True,
args=(temp_dir, new_server_dir, port, new_id),
name=f"{new_id}_java_zip_import",
)
import_thread.start()
def import_threaded_java_zip_server(self, temp_dir, new_server_dir, port, new_id):
has_properties = False
# extracts archive to temp directory
for item in os.listdir(temp_dir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(temp_dir, item)):
FileHelpers.move_file(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
else:
if item != "db_stats":
FileHelpers.move_dir(
os.path.join(temp_dir, item),
os.path.join(new_server_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
server_users = PermissionsServers.get_server_user_list(new_id)
ServersController.finish_import(new_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {})
# deletes temp dir
FileHelpers.del_dirs(temp_dir)
def import_bedrock_server(
self, server_path, new_server_dir, port, full_jar_path, new_id
):
import_thread = threading.Thread(
target=self.import_threaded_bedrock_server,
daemon=True,
args=(server_path, new_server_dir, port, full_jar_path, new_id),
name=f"{new_id}_bedrock_import",
)
import_thread.start()
def import_threaded_bedrock_server(
self, server_path, new_server_dir, port, full_jar_path, new_id
):
for item in os.listdir(server_path):
if not item == "db_stats":
try:
if os.path.isdir(os.path.join(server_path, item)):
FileHelpers.copy_dir(
os.path.join(server_path, item),
os.path.join(new_server_dir, item),
)
else:
FileHelpers.copy_file(
os.path.join(server_path, item),
os.path.join(new_server_dir, item),
)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
if os.name != "nt":
if Helpers.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2760)
ServersController.finish_import(new_id)
server_users = PermissionsServers.get_server_user_list(new_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {})
def import_bedrock_zip_server(
self, temp_dir, new_server_dir, full_jar_path, port, new_id
):
import_thread = threading.Thread(
target=self.import_threaded_bedrock_zip_server,
daemon=True,
args=(temp_dir, new_server_dir, full_jar_path, port, new_id),
name=f"{new_id}_bedrock_import",
)
import_thread.start()
def import_threaded_bedrock_zip_server(
self, temp_dir, new_server_dir, full_jar_path, port, new_id
):
has_properties = False
# extracts archive to temp directory
for item in os.listdir(temp_dir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(temp_dir, item)):
FileHelpers.move_file(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
else:
if item != "db_stats":
FileHelpers.move_dir(
os.path.join(temp_dir, item),
os.path.join(new_server_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
ServersController.finish_import(new_id)
server_users = PermissionsServers.get_server_user_list(new_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {})
if os.name != "nt":
if Helpers.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2760)
# deletes temp dir
FileHelpers.del_dirs(temp_dir)

View File

@ -1,6 +1,7 @@
import os import os
import pathlib import pathlib
from pathlib import Path from pathlib import Path
from datetime import datetime
import platform import platform
import shutil import shutil
import time import time
@ -28,15 +29,17 @@ from app.classes.shared.authentication import Authentication
from app.classes.shared.console import Console from app.classes.shared.console import Console
from app.classes.shared.helpers import Helpers from app.classes.shared.helpers import Helpers
from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.import_helper import ImportHelpers
from app.classes.minecraft.serverjars import ServerJars from app.classes.minecraft.serverjars import ServerJars
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Controller: class Controller:
def __init__(self, database, helper, file_helper): def __init__(self, database, helper, file_helper, import_helper):
self.helper: Helpers = helper self.helper: Helpers = helper
self.file_helper: FileHelpers = file_helper self.file_helper: FileHelpers = file_helper
self.import_helper: ImportHelpers = import_helper
self.server_jars: ServerJars = ServerJars(helper) self.server_jars: ServerJars = ServerJars(helper)
self.users_helper: HelperUsers = HelperUsers(database, self.helper) self.users_helper: HelperUsers = HelperUsers(database, self.helper)
self.roles_helper: HelperRoles = HelperRoles(database) self.roles_helper: HelperRoles = HelperRoles(database)
@ -178,18 +181,20 @@ class Controller:
) )
# Make version file .txt when we download it for support # Make version file .txt when we download it for support
# Most people have a default editor for .txt also more mobile friendly... # Most people have a default editor for .txt also more mobile friendly...
FileHelpers.copy_file( sys_info_string = (
os.path.join(self.project_root, "app", "config", "version.json"), f"Crafty v{self.helper.get_version_string()} Support Logs\n"
os.path.join(temp_dir, "crafty_sys_info.txt"), f"\n"
f"OS Info:- \n"
f"OS: {str(platform.system())}\n"
f"Version: {str(platform.release())}"
f"\n \n"
f"Log archive created on: {datetime.now()}"
) )
with open( with open(
os.path.join(temp_dir, "crafty_sys_info.txt"), "a", encoding="utf-8" os.path.join(temp_dir, "crafty_sys_info.txt"), "a", encoding="utf-8"
) as f: ) as f:
f.write("\n") f.write(sys_info_string)
f.write("OS Info:\n") FileHelpers.make_compressed_archive(temp_zip_storage, temp_dir, sys_info_string)
f.write("OS: " + str(platform.system()) + "\n")
f.write("Version: " + str(platform.release()))
FileHelpers.make_compressed_archive(temp_zip_storage, temp_dir)
if len(self.helper.websocket_helper.clients) > 0: if len(self.helper.websocket_helper.clients) > 0:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
exec_user["user_id"], exec_user["user_id"],
@ -244,7 +249,7 @@ class Controller:
except: except:
return {"percent": 0, "total_files": 0} return {"percent": 0, "total_files": 0}
def create_api_server(self, data: dict): def create_api_server(self, data: dict, user_id):
server_fs_uuid = Helpers.create_uuid() server_fs_uuid = Helpers.create_uuid()
new_server_path = os.path.join(self.helper.servers_dir, server_fs_uuid) new_server_path = os.path.join(self.helper.servers_dir, server_fs_uuid)
backup_path = os.path.join(self.helper.backup_path, server_fs_uuid) backup_path = os.path.join(self.helper.backup_path, server_fs_uuid)
@ -307,7 +312,9 @@ class Controller:
# TODO: Copy files from the zip file to the new server directory # TODO: Copy files from the zip file to the new server directory
server_file = create_data["jarfile"] server_file = create_data["jarfile"]
raise Exception("Not yet implemented") raise Exception("Not yet implemented")
_create_server_properties_if_needed(create_data["server_properties_port"]) _create_server_properties_if_needed(
create_data["server_properties_port"],
)
min_mem = create_data["mem_min"] min_mem = create_data["mem_min"]
max_mem = create_data["mem_max"] max_mem = create_data["mem_max"]
@ -403,6 +410,7 @@ class Controller:
server_log_file=log_location, server_log_file=log_location,
server_stop=stop_command, server_stop=stop_command,
server_port=monitoring_port, server_port=monitoring_port,
created_by=user_id,
server_host=monitoring_host, server_host=monitoring_host,
server_type=monitoring_type, server_type=monitoring_type,
) )
@ -429,6 +437,7 @@ class Controller:
min_mem: int, min_mem: int,
max_mem: int, max_mem: int,
port: int, port: int,
user_id: int,
): ):
server_id = Helpers.create_uuid() server_id = Helpers.create_uuid()
server_dir = os.path.join(self.helper.servers_dir, server_id) server_dir = os.path.join(self.helper.servers_dir, server_id)
@ -489,6 +498,7 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
port, port,
user_id,
server_type="minecraft-java", server_type="minecraft-java",
) )
@ -524,6 +534,7 @@ class Controller:
min_mem: int, min_mem: int,
max_mem: int, max_mem: int,
port: int, port: int,
user_id: int,
): ):
server_id = Helpers.create_uuid() server_id = Helpers.create_uuid()
new_server_dir = os.path.join(self.helper.servers_dir, server_id) new_server_dir = os.path.join(self.helper.servers_dir, server_id)
@ -537,25 +548,6 @@ class Controller:
Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(new_server_dir)
Helpers.ensure_dir_exists(backup_path) Helpers.ensure_dir_exists(backup_path)
server_path = Helpers.get_os_understandable_path(server_path) server_path = Helpers.get_os_understandable_path(server_path)
try:
FileHelpers.copy_dir(server_path, new_server_dir, True)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
full_jar_path = os.path.join(new_server_dir, server_jar) full_jar_path = os.path.join(new_server_dir, server_jar)
@ -584,8 +576,11 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
port, port,
user_id,
server_type="minecraft-java", server_type="minecraft-java",
) )
ServersController.set_import(new_id)
self.import_helper.import_jar_server(server_path, new_server_dir, port, new_id)
return new_id return new_id
def import_zip_server( def import_zip_server(
@ -596,6 +591,7 @@ class Controller:
min_mem: int, min_mem: int,
max_mem: int, max_mem: int,
port: int, port: int,
user_id: int,
): ):
server_id = Helpers.create_uuid() server_id = Helpers.create_uuid()
new_server_dir = os.path.join(self.helper.servers_dir, server_id) new_server_dir = os.path.join(self.helper.servers_dir, server_id)
@ -609,32 +605,6 @@ class Controller:
temp_dir = Helpers.get_os_understandable_path(zip_path) temp_dir = Helpers.get_os_understandable_path(zip_path)
Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(new_server_dir)
Helpers.ensure_dir_exists(backup_path) Helpers.ensure_dir_exists(backup_path)
has_properties = False
# extracts archive to temp directory
for item in os.listdir(temp_dir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(temp_dir, item)):
FileHelpers.move_file(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
else:
FileHelpers.move_dir(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
full_jar_path = os.path.join(new_server_dir, server_jar) full_jar_path = os.path.join(new_server_dir, server_jar)
@ -664,8 +634,13 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
port, port,
user_id,
server_type="minecraft-java", server_type="minecraft-java",
) )
ServersController.set_import(new_id)
self.import_helper.import_java_zip_server(
temp_dir, new_server_dir, port, new_id
)
return new_id return new_id
# ********************************************************************************** # **********************************************************************************
@ -673,7 +648,12 @@ class Controller:
# ********************************************************************************** # **********************************************************************************
def import_bedrock_server( def import_bedrock_server(
self, server_name: str, server_path: str, server_exe: str, port: int self,
server_name: str,
server_path: str,
server_exe: str,
port: int,
user_id: int,
): ):
server_id = Helpers.create_uuid() server_id = Helpers.create_uuid()
new_server_dir = os.path.join(self.helper.servers_dir, server_id) new_server_dir = os.path.join(self.helper.servers_dir, server_id)
@ -687,25 +667,6 @@ class Controller:
Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(new_server_dir)
Helpers.ensure_dir_exists(backup_path) Helpers.ensure_dir_exists(backup_path)
server_path = Helpers.get_os_understandable_path(server_path) server_path = Helpers.get_os_understandable_path(server_path)
try:
FileHelpers.copy_dir(server_path, new_server_dir, True)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
full_jar_path = os.path.join(new_server_dir, server_exe) full_jar_path = os.path.join(new_server_dir, server_exe)
@ -727,15 +688,22 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
port, port,
user_id,
server_type="minecraft-bedrock", server_type="minecraft-bedrock",
) )
if os.name != "nt": ServersController.set_import(new_id)
if Helpers.check_file_exists(full_jar_path): self.import_helper.import_bedrock_server(
os.chmod(full_jar_path, 0o2760) server_path, new_server_dir, port, full_jar_path, new_id
)
return new_id return new_id
def import_bedrock_zip_server( def import_bedrock_zip_server(
self, server_name: str, zip_path: str, server_exe: str, port: int self,
server_name: str,
zip_path: str,
server_exe: str,
port: int,
user_id: int,
): ):
server_id = Helpers.create_uuid() server_id = Helpers.create_uuid()
new_server_dir = os.path.join(self.helper.servers_dir, server_id) new_server_dir = os.path.join(self.helper.servers_dir, server_id)
@ -749,32 +717,6 @@ class Controller:
temp_dir = Helpers.get_os_understandable_path(zip_path) temp_dir = Helpers.get_os_understandable_path(zip_path)
Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(new_server_dir)
Helpers.ensure_dir_exists(backup_path) Helpers.ensure_dir_exists(backup_path)
has_properties = False
# extracts archive to temp directory
for item in os.listdir(temp_dir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(temp_dir, item)):
FileHelpers.move_file(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
else:
FileHelpers.move_dir(
os.path.join(temp_dir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as file:
file.write(f"server-port={port}")
file.close()
full_jar_path = os.path.join(new_server_dir, server_exe) full_jar_path = os.path.join(new_server_dir, server_exe)
@ -796,8 +738,12 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
port, port,
user_id,
server_type="minecraft-bedrock", server_type="minecraft-bedrock",
) )
self.import_helper.import_bedrock_zip_server(
temp_dir, new_server_dir, full_jar_path, port, new_id
)
if os.name != "nt": if os.name != "nt":
if Helpers.check_file_exists(full_jar_path): if Helpers.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2760) os.chmod(full_jar_path, 0o2760)
@ -838,6 +784,7 @@ class Controller:
server_log_file: str, server_log_file: str,
server_stop: str, server_stop: str,
server_port: int, server_port: int,
created_by: int,
server_type: str, server_type: str,
server_host: str = "127.0.0.1", server_host: str = "127.0.0.1",
): ):
@ -852,6 +799,7 @@ class Controller:
server_log_file, server_log_file,
server_stop, server_stop,
server_type, server_type,
created_by,
server_port, server_port,
server_host, server_host,
) )

View File

@ -26,6 +26,7 @@ class DatabaseBuilder:
password=password, password=password,
email="default@example.com", email="default@example.com",
superuser=True, superuser=True,
manager=None,
) )
def is_fresh_install(self): def is_fresh_install(self):

View File

@ -293,7 +293,7 @@ class ServerInstance:
else: else:
user_lang = HelperUsers.get_user_lang_by_id(user_id) user_lang = HelperUsers.get_user_lang_by_id(user_id)
if self.stats_helper.get_download_status(): if self.stats_helper.get_import_status():
if user_id: if user_id:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user_id, user_id,
@ -612,21 +612,25 @@ class ServerInstance:
# caching the name and pid number # caching the name and pid number
server_name = self.name server_name = self.name
server_pid = self.process.pid server_pid = self.process.pid
self.shutdown_timeout = self.settings["shutdown_timeout"]
while running: while running:
i += 1 i += 1
logstr = ( ttk = int(self.shutdown_timeout - (i * 2))
f"Server {server_name} is still running " if i <= self.shutdown_timeout / 2:
f"- waiting 2s to see if it stops ({int(60-(i*2))} " logstr = (
f"seconds until force close)" f"Server {server_name} is still running "
) "- waiting 2s to see if it stops"
logger.info(logstr) f"({ttk} "
Console.info(logstr) f"seconds until force close)"
)
logger.info(logstr)
Console.info(logstr)
running = self.check_running() running = self.check_running()
time.sleep(2) time.sleep(2)
# if we haven't closed in 60 seconds, let's just slam down on the PID # if we haven't closed in 60 seconds, let's just slam down on the PID
if i >= 30: if i >= round(self.shutdown_timeout / 2, 0):
logger.info( logger.info(
f"Server {server_name} is still running - Forcing the process down" f"Server {server_name} is still running - Forcing the process down"
) )
@ -1223,6 +1227,7 @@ class ServerInstance:
"version": raw_ping_result.get("version"), "version": raw_ping_result.get("version"),
"icon": raw_ping_result.get("icon"), "icon": raw_ping_result.get("icon"),
"crashed": self.is_crashed, "crashed": self.is_crashed,
"created": datetime.datetime.now().strftime("%Y/%m/%d, %H:%M:%S"),
}, },
) )
total_players += int(raw_ping_result.get("online")) total_players += int(raw_ping_result.get("online"))

View File

@ -394,6 +394,7 @@ class AjaxHandler(BaseHandler):
"1", "1",
"2", "2",
server_data["server_port"], server_data["server_port"],
server_data["created_by"],
) )
new_server_id = new_server new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server) new_server = self.controller.servers.get_server_data(new_server)
@ -416,6 +417,7 @@ class AjaxHandler(BaseHandler):
temp_dir, temp_dir,
server_data["executable"], server_data["executable"],
server_data["server_port"], server_data["server_port"],
server_data["created_by"],
) )
new_server_id = new_server new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server) new_server = self.controller.servers.get_server_data(new_server)

View File

@ -340,10 +340,11 @@ class CreateUser(ApiHandler):
new_username = self.get_argument("username").lower() new_username = self.get_argument("username").lower()
new_pass = self.get_argument("password") new_pass = self.get_argument("password")
manager = int(user_obj["user_id"])
if new_username: if new_username:
self.controller.users.add_user( self.controller.users.add_user(
new_username, new_pass, "default@example.com", True, False new_username, manager, new_pass, "default@example.com", True, False
) )
self.return_response( self.return_response(

View File

@ -453,8 +453,8 @@ class PanelHandler(BaseHandler):
for server in un_used_servers[:]: for server in un_used_servers[:]:
if flag == 0: if flag == 0:
server["stats"][ server["stats"][
"downloading" "importing"
] = self.controller.servers.get_download_status( ] = self.controller.servers.get_import_status(
str(server["stats"]["server_id"]["server_id"]) str(server["stats"]["server_id"]["server_id"])
) )
server["stats"]["crashed"] = self.controller.servers.is_crashed( server["stats"]["crashed"] = self.controller.servers.is_crashed(
@ -524,6 +524,7 @@ class PanelHandler(BaseHandler):
"files", "files",
"admin_controls", "admin_controls",
"schedules", "schedules",
"metrics",
] ]
if not self.failed_server: if not self.failed_server:
server = self.controller.servers.get_server_instance_by_id(server_id) server = self.controller.servers.get_server_instance_by_id(server_id)
@ -571,11 +572,11 @@ class PanelHandler(BaseHandler):
"started": "False", "started": "False",
} }
if not self.failed_server: if not self.failed_server:
page_data["downloading"] = self.controller.servers.get_download_status( page_data["importing"] = self.controller.servers.get_import_status(
server_id server_id
) )
else: else:
page_data["downloading"] = False page_data["importing"] = False
page_data["server_id"] = server_id page_data["server_id"] = server_id
try: try:
page_data["waiting_start"] = self.controller.servers.get_waiting_start( page_data["waiting_start"] = self.controller.servers.get_waiting_start(
@ -754,6 +755,11 @@ class PanelHandler(BaseHandler):
page_data["backup_list"] = [] page_data["backup_list"] = []
page_data["backup_path"] = Helpers.wtol_path(server_info["backup_path"]) page_data["backup_path"] = Helpers.wtol_path(server_info["backup_path"])
if subpage == "metrics":
page_data["history_stats"] = self.controller.servers.get_history_stats(
server_id
)
def get_banned_players_html(): def get_banned_players_html():
banned_players = self.controller.servers.get_banned_players(server_id) banned_players = self.controller.servers.get_banned_players(server_id)
if banned_players is None: if banned_players is None:
@ -860,6 +866,18 @@ class PanelHandler(BaseHandler):
page_data["users"] = self.controller.users.get_all_users() page_data["users"] = self.controller.users.get_all_users()
page_data["roles"] = self.controller.roles.get_all_roles() page_data["roles"] = self.controller.roles.get_all_roles()
page_data["auth-servers"][user.user_id] = super_auth_servers page_data["auth-servers"][user.user_id] = super_auth_servers
page_data["managed_users"] = []
else:
page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"]
)
page_data["assigned_roles"] = []
for item in page_data["roles"]:
page_data["assigned_roles"].append(item.role_id)
page_data["managed_roles"] = self.controller.users.get_managed_roles(
exec_user["user_id"]
)
template = "panel/panel_config.html" template = "panel/panel_config.html"
@ -885,7 +903,7 @@ class PanelHandler(BaseHandler):
) )
return return
page_data["roles_all"] = self.controller.roles.get_all_roles() page_data["roles"] = self.controller.roles.get_all_roles()
page_data["servers"] = [] page_data["servers"] = []
page_data["servers_all"] = self.controller.servers.get_all_defined_servers() page_data["servers_all"] = self.controller.servers.get_all_defined_servers()
page_data["role-servers"] = [] page_data["role-servers"] = []
@ -904,8 +922,16 @@ class PanelHandler(BaseHandler):
) )
if superuser: if superuser:
page_data["super-disabled"] = "" page_data["super-disabled"] = ""
page_data["users"] = self.controller.users.get_all_users()
else: else:
page_data["super-disabled"] = "disabled" page_data["super-disabled"] = "disabled"
page_data["exec_user"] = exec_user["user_id"]
page_data["manager"] = {
"user_id": -100,
"username": "None",
}
for file in sorted( for file in sorted(
os.listdir(os.path.join(self.helper.root_dir, "app", "translations")) os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
): ):
@ -1074,9 +1100,21 @@ class PanelHandler(BaseHandler):
page_data["user"] = self.controller.users.get_user_by_id(user_id) page_data["user"] = self.controller.users.get_user_by_id(user_id)
page_data["servers"] = set() page_data["servers"] = set()
page_data["role-servers"] = page_role_servers page_data["role-servers"] = page_role_servers
page_data["roles_all"] = self.controller.roles.get_all_roles() page_data["roles"] = self.controller.roles.get_all_roles()
page_data["exec_user"] = exec_user["user_id"]
page_data["servers_all"] = self.controller.servers.get_all_defined_servers() page_data["servers_all"] = self.controller.servers.get_all_defined_servers()
page_data["superuser"] = superuser page_data["superuser"] = superuser
if page_data["user"]["manager"] is not None:
page_data["manager"] = self.controller.users.get_user_by_id(
page_data["user"]["manager"]
)
else:
page_data["manager"] = {
"user_id": -100,
"username": "None",
}
if exec_user["superuser"]:
page_data["users"] = self.controller.users.get_all_users()
page_data[ page_data[
"permissions_all" "permissions_all"
] = self.controller.crafty_perms.list_defined_crafty_permissions() ] = self.controller.crafty_perms.list_defined_crafty_permissions()
@ -1115,6 +1153,17 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: not a user editor" "/panel/error?error=Unauthorized access: not a user editor"
) )
return return
if (
(
self.controller.users.get_user_by_id(user_id)["manager"]
!= exec_user["user_id"]
)
and not exec_user["superuser"]
and str(exec_user["user_id"]) != str(user_id)
):
self.redirect(
"/panel/error?error=Unauthorized access: you cannot edit this user"
)
page_data["servers"] = [] page_data["servers"] = []
page_data["role-servers"] = [] page_data["role-servers"] = []
@ -1212,6 +1261,11 @@ class PanelHandler(BaseHandler):
defined_servers = self.controller.servers.get_authorized_servers( defined_servers = self.controller.servers.get_authorized_servers(
exec_user["user_id"] exec_user["user_id"]
) )
page_data["role_manager"] = {
"user_id": -100,
"username": "None",
}
page_servers = [] page_servers = []
for server in defined_servers: for server in defined_servers:
if server not in page_servers: if server not in page_servers:
@ -1229,6 +1283,7 @@ class PanelHandler(BaseHandler):
user_roles = self.get_user_roles() user_roles = self.get_user_roles()
page_data["new_role"] = False page_data["new_role"] = False
role_id = self.get_argument("id", None) role_id = self.get_argument("id", None)
role = self.controller.roles.get_role(role_id)
page_data["role"] = self.controller.roles.get_role_with_servers(role_id) page_data["role"] = self.controller.roles.get_role_with_servers(role_id)
if exec_user["superuser"]: if exec_user["superuser"]:
defined_servers = self.controller.servers.list_defined_servers() defined_servers = self.controller.servers.list_defined_servers()
@ -1252,7 +1307,21 @@ class PanelHandler(BaseHandler):
page_data["user-roles"] = user_roles page_data["user-roles"] = user_roles
page_data["users"] = self.controller.users.get_all_users() page_data["users"] = self.controller.users.get_all_users()
if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: if page_data["role"]["manager"] is not None:
page_data["role_manager"] = self.controller.users.get_user_by_id(
page_data["role"]["manager"]
)
else:
page_data["role_manager"] = {
"user_id": -100,
"username": "None",
}
if (
EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions
or exec_user["user_id"] != role["manager"]
and not exec_user["superuser"]
):
self.redirect( self.redirect(
"/panel/error?error=Unauthorized access: not a role editor" "/panel/error?error=Unauthorized access: not a role editor"
) )
@ -1266,8 +1335,15 @@ class PanelHandler(BaseHandler):
elif page == "remove_role": elif page == "remove_role":
role_id = bleach.clean(self.get_argument("id", None)) role_id = bleach.clean(self.get_argument("id", None))
if not superuser: if (
self.redirect("/panel/error?error=Unauthorized access: not superuser") not superuser
and self.controller.roles.get_role(role_id)["manager"]
!= exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: not superuser not"
" role manager"
)
return return
if role_id is None: if role_id is None:
self.redirect("/panel/error?error=Invalid Role ID") self.redirect("/panel/error?error=Invalid Role ID")
@ -1418,6 +1494,7 @@ class PanelHandler(BaseHandler):
return return
server_name = self.get_argument("server_name", None) server_name = self.get_argument("server_name", None)
server_obj = self.controller.servers.get_server_obj(server_id) server_obj = self.controller.servers.get_server_obj(server_id)
shutdown_timeout = self.get_argument("shutdown_timeout", 60)
if superuser: if superuser:
server_path = self.get_argument("server_path", None) server_path = self.get_argument("server_path", None)
if Helpers.is_os_windows(): if Helpers.is_os_windows():
@ -1498,6 +1575,7 @@ class PanelHandler(BaseHandler):
) )
server_obj.server_name = server_name server_obj.server_name = server_name
server_obj.shutdown_timeout = shutdown_timeout
if superuser: if superuser:
if Helpers.validate_traversal( if Helpers.validate_traversal(
self.helper.get_servers_root_dir(), server_path self.helper.get_servers_root_dir(), server_path
@ -1930,6 +2008,7 @@ class PanelHandler(BaseHandler):
"system user is not editable" "system user is not editable"
) )
user_id = bleach.clean(self.get_argument("id", None)) user_id = bleach.clean(self.get_argument("id", None))
user = self.controller.users.get_user_by_id(user_id)
username = bleach.clean(self.get_argument("username", None).lower()) username = bleach.clean(self.get_argument("username", None).lower())
if ( if (
username != self.controller.users.get_user_by_id(user_id)["username"] username != self.controller.users.get_user_by_id(user_id)["username"]
@ -1962,7 +2041,19 @@ class PanelHandler(BaseHandler):
else: else:
superuser = 0 superuser = 0
if not exec_user["superuser"]: if exec_user["superuser"]:
manager = self.get_argument("manager")
if manager == "":
manager = None
else:
manager = int(manager)
else:
manager = user["manager"]
if (
not exec_user["superuser"]
and int(exec_user["user_id"]) != user["manager"]
):
if username is None or username == "": if username is None or username == "":
self.redirect("/panel/error?error=Invalid username") self.redirect("/panel/error?error=Invalid username")
return return
@ -2013,6 +2104,7 @@ class PanelHandler(BaseHandler):
user_data = { user_data = {
"username": username, "username": username,
"manager": manager,
"password": password0, "password": password0,
"email": email, "email": email,
"enabled": enabled, "enabled": enabled,
@ -2029,13 +2121,13 @@ class PanelHandler(BaseHandler):
user_id, user_data=user_data, user_crafty_data=user_crafty_data user_id, user_data=user_data, user_crafty_data=user_crafty_data
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
f"Edited user {username} (UID:{user_id}) with roles {roles} " f"Edited user {username} (UID:{user_id}) with roles {roles} "
f"and permissions {permissions_mask}", f"and permissions {permissions_mask}",
server_id=0, server_id=0,
source_ip=self.get_remote_ip(), source_ip=self.get_remote_ip(),
) )
self.redirect("/panel/panel_config") self.redirect("/panel/panel_config")
elif page == "edit_user_apikeys": elif page == "edit_user_apikeys":
@ -2158,6 +2250,15 @@ class PanelHandler(BaseHandler):
if username is None or username == "": if username is None or username == "":
self.redirect("/panel/error?error=Invalid username") self.redirect("/panel/error?error=Invalid username")
return return
if exec_user["superuser"]:
manager = self.get_argument("manager")
if manager == "":
manager = None
else:
manager = int(manager)
else:
manager = int(exec_user["user_id"])
# does this user id exist? # does this user id exist?
if self.controller.users.get_id_by_name(username) is not None: if self.controller.users.get_id_by_name(username) is not None:
self.redirect("/panel/error?error=User exists") self.redirect("/panel/error?error=User exists")
@ -2172,6 +2273,7 @@ class PanelHandler(BaseHandler):
user_id = self.controller.users.add_user( user_id = self.controller.users.add_user(
username, username,
manager=manager,
password=password0, password=password0,
email=email, email=email,
enabled=enabled, enabled=enabled,
@ -2198,14 +2300,19 @@ class PanelHandler(BaseHandler):
server_id=0, server_id=0,
source_ip=self.get_remote_ip(), source_ip=self.get_remote_ip(),
) )
self.controller.crafty_perms.add_user_creation(exec_user["user_id"])
self.redirect("/panel/panel_config") self.redirect("/panel/panel_config")
elif page == "edit_role": elif page == "edit_role":
role_id = bleach.clean(self.get_argument("id", None)) role_id = bleach.clean(self.get_argument("id", None))
role_name = bleach.clean(self.get_argument("role_name", None)) role_name = bleach.clean(self.get_argument("role_name", None))
if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: role = self.controller.roles.get_role(role_id)
if (
EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions
and exec_user["user_id"] != role["manager"]
and not exec_user["superuser"]
):
self.redirect( self.redirect(
"/panel/error?error=Unauthorized access: not a role editor" "/panel/error?error=Unauthorized access: not a role editor"
) )
@ -2221,9 +2328,18 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Role ID") self.redirect("/panel/error?error=Invalid Role ID")
return return
if exec_user["superuser"]:
manager = self.get_argument("manager", None)
if manager == "":
manager = None
else:
manager = role["manager"]
servers = self.get_role_servers() servers = self.get_role_servers()
self.controller.roles.update_role_advanced(role_id, role_name, servers) self.controller.roles.update_role_advanced(
role_id, role_name, servers, manager
)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -2235,6 +2351,12 @@ class PanelHandler(BaseHandler):
elif page == "add_role": elif page == "add_role":
role_name = bleach.clean(self.get_argument("role_name", None)) role_name = bleach.clean(self.get_argument("role_name", None))
if exec_user["superuser"]:
manager = self.get_argument("manager", None)
if manager == "":
manager = None
else:
manager = exec_user["user_id"]
if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions:
self.redirect( self.redirect(
@ -2259,7 +2381,9 @@ class PanelHandler(BaseHandler):
servers = self.get_role_servers() servers = self.get_role_servers()
role_id = self.controller.roles.add_role_advanced(role_name, servers) role_id = self.controller.roles.add_role_advanced(
role_name, servers, manager
)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -2267,7 +2391,6 @@ class PanelHandler(BaseHandler):
server_id=0, server_id=0,
source_ip=self.get_remote_ip(), source_ip=self.get_remote_ip(),
) )
self.controller.crafty_perms.add_role_creation(exec_user["user_id"])
self.redirect("/panel/panel_config") self.redirect("/panel/panel_config")
else: else:

View File

@ -116,7 +116,9 @@ class ApiRolesIndexHandler(BaseApiHandler):
400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"} 400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"}
) )
role_id = self.controller.roles.add_role_advanced(role_name, servers) role_id = self.controller.roles.add_role_advanced(
role_name, servers, user["user_id"]
)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
user["user_id"], user["user_id"],

View File

@ -665,10 +665,9 @@ class ApiServersIndexHandler(BaseApiHandler):
}, },
) )
new_server_id, new_server_uuid = self.controller.create_api_server(data) new_server_id, new_server_uuid = self.controller.create_api_server(
data, user["user_id"]
# Increase the server creation counter )
self.controller.crafty_perms.add_server_creation(user["user_id"])
self.controller.servers.stats.record_stats() self.controller.servers.stats.record_stats()

View File

@ -84,6 +84,7 @@ class ApiServersServerActionHandler(BaseApiHandler):
new_server_log_file, new_server_log_file,
server_data.get("stop_command"), server_data.get("stop_command"),
server_data.get("type"), server_data.get("type"),
user_id,
server_data.get("server_port"), server_data.get("server_port"),
) )

View File

@ -96,6 +96,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
username = data["username"] username = data["username"]
username = str(username).lower() username = str(username).lower()
manager = int(user["user_id"])
password = data["password"] password = data["password"]
email = data.get("email", "default@example.com") email = data.get("email", "default@example.com")
enabled = data.get("enabled", True) enabled = data.get("enabled", True)
@ -149,6 +150,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
# TODO: do this in the most efficient way # TODO: do this in the most efficient way
user_id = self.controller.users.add_user( user_id = self.controller.users.add_user(
username, username,
manager,
password, password,
email, email,
enabled, enabled,

View File

@ -1,6 +1,7 @@
import json import json
import logging import logging
import os import os
import time
import tornado.web import tornado.web
import tornado.escape import tornado.escape
import bleach import bleach
@ -224,6 +225,23 @@ class ServerHandler(BaseHandler):
if server_id is not None: if server_id is not None:
if command == "clone_server": if command == "clone_server":
if (
not superuser
and not self.controller.crafty_perms.can_create_server(
exec_user["user_id"]
)
):
time.sleep(3)
self.helper.websocket_helper.broadcast_user(
exec_user["user_id"],
"send_start_error",
{
"error": "<i class='fas fa-exclamation-triangle'"
" style='font-size:48px;color:red'>"
"</i> Not a server creator or server limit reached."
},
)
return
def is_name_used(name): def is_name_used(name):
for server in self.controller.servers.get_all_defined_servers(): for server in self.controller.servers.get_all_defined_servers():
@ -231,6 +249,7 @@ class ServerHandler(BaseHandler):
return True return True
return return
template = "/panel/dashboard"
server_data = self.controller.servers.get_server_data_by_id( server_data = self.controller.servers.get_server_data_by_id(
server_id server_id
) )
@ -265,6 +284,7 @@ class ServerHandler(BaseHandler):
backup_path = os.path.join(self.helper.backup_path, new_server_uuid) backup_path = os.path.join(self.helper.backup_path, new_server_uuid)
server_port = server_data.get("server_port") server_port = server_data.get("server_port")
server_type = server_data.get("type") server_type = server_data.get("type")
created_by = exec_user["user_id"]
new_server_id = self.controller.servers.create_server( new_server_id = self.controller.servers.create_server(
new_server_name, new_server_name,
@ -276,6 +296,7 @@ class ServerHandler(BaseHandler):
new_server_log_file, new_server_log_file,
stop_command, stop_command,
server_type, server_type,
created_by,
server_port, server_port,
) )
if not exec_user["superuser"]: if not exec_user["superuser"]:
@ -283,7 +304,8 @@ class ServerHandler(BaseHandler):
new_server_id new_server_id
).get("server_uuid") ).get("server_uuid")
role_id = self.controller.roles.add_role( role_id = self.controller.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}" f"Creator of Server with uuid={new_server_uuid}",
exec_user["user_id"],
) )
self.controller.server_perms.add_role_server( self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111" new_server_id, role_id, "11111111"
@ -291,9 +313,6 @@ class ServerHandler(BaseHandler):
self.controller.users.add_role_to_user( self.controller.users.add_role_to_user(
exec_user["user_id"], role_id exec_user["user_id"], role_id
) )
self.controller.crafty_perms.add_server_creation(
exec_user["user_id"]
)
self.controller.servers.init_all_servers() self.controller.servers.init_all_servers()
@ -353,6 +372,7 @@ class ServerHandler(BaseHandler):
min_mem, min_mem,
max_mem, max_mem,
port, port,
exec_user["user_id"],
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -369,7 +389,13 @@ class ServerHandler(BaseHandler):
return return
new_server_id = self.controller.import_zip_server( new_server_id = self.controller.import_zip_server(
server_name, zip_path, import_server_jar, min_mem, max_mem, port server_name,
zip_path,
import_server_jar,
min_mem,
max_mem,
port,
exec_user["user_id"],
) )
if new_server_id == "false": if new_server_id == "false":
self.redirect( self.redirect(
@ -385,8 +411,6 @@ class ServerHandler(BaseHandler):
new_server_id, new_server_id,
self.get_remote_ip(), self.get_remote_ip(),
) )
# deletes temp dir
FileHelpers.del_dirs(zip_path)
else: else:
if len(server_parts) != 3: if len(server_parts) != 3:
self.redirect("/panel/error?error=Invalid server data") self.redirect("/panel/error?error=Invalid server data")
@ -402,6 +426,7 @@ class ServerHandler(BaseHandler):
min_mem, min_mem,
max_mem, max_mem,
port, port,
exec_user["user_id"],
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -420,7 +445,8 @@ class ServerHandler(BaseHandler):
new_server_id new_server_id
).get("server_uuid") ).get("server_uuid")
role_id = self.controller.roles.add_role( role_id = self.controller.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}" f"Creator of Server with uuid={new_server_uuid}",
exec_user["user_id"],
) )
self.controller.server_perms.add_role_server( self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111" new_server_id, role_id, "11111111"
@ -428,9 +454,6 @@ class ServerHandler(BaseHandler):
self.controller.users.add_role_to_user( self.controller.users.add_role_to_user(
exec_user["user_id"], role_id exec_user["user_id"], role_id
) )
self.controller.crafty_perms.add_server_creation(
exec_user["user_id"]
)
else: else:
for role in captured_roles: for role in captured_roles:
@ -483,7 +506,11 @@ class ServerHandler(BaseHandler):
return return
new_server_id = self.controller.import_bedrock_server( new_server_id = self.controller.import_bedrock_server(
server_name, import_server_path, import_server_exe, port server_name,
import_server_path,
import_server_exe,
port,
exec_user["user_id"],
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -500,7 +527,11 @@ class ServerHandler(BaseHandler):
return return
new_server_id = self.controller.import_bedrock_zip_server( new_server_id = self.controller.import_bedrock_zip_server(
server_name, zip_path, import_server_exe, port server_name,
zip_path,
import_server_exe,
port,
exec_user["user_id"],
) )
if new_server_id == "false": if new_server_id == "false":
self.redirect( self.redirect(
@ -516,8 +547,6 @@ class ServerHandler(BaseHandler):
new_server_id, new_server_id,
self.get_remote_ip(), self.get_remote_ip(),
) )
# deletes temp dir
FileHelpers.del_dirs(zip_path)
else: else:
if len(server_parts) != 2: if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data") self.redirect("/panel/error?error=Invalid server data")
@ -526,7 +555,13 @@ class ServerHandler(BaseHandler):
# TODO: add server type check here and call the correct server # TODO: add server type check here and call the correct server
# add functions if not a jar # add functions if not a jar
new_server_id = self.controller.create_jar_server( new_server_id = self.controller.create_jar_server(
server_type, server_version, server_name, min_mem, max_mem, port server_type,
server_version,
server_name,
min_mem,
max_mem,
port,
exec_user["user_id"],
) )
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
exec_user["user_id"], exec_user["user_id"],
@ -545,7 +580,8 @@ class ServerHandler(BaseHandler):
new_server_id new_server_id
).get("server_uuid") ).get("server_uuid")
role_id = self.controller.roles.add_role( role_id = self.controller.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}" f"Creator of Server with uuid={new_server_uuid}",
exec_user["user_id"],
) )
self.controller.server_perms.add_role_server( self.controller.server_perms.add_role_server(
new_server_id, role_id, "11111111" new_server_id, role_id, "11111111"
@ -553,9 +589,6 @@ class ServerHandler(BaseHandler):
self.controller.users.add_role_to_user( self.controller.users.add_role_to_user(
exec_user["user_id"], role_id exec_user["user_id"], role_id
) )
self.controller.crafty_perms.add_server_creation(
exec_user["user_id"]
)
else: else:
for role in captured_roles: for role in captured_roles:

View File

@ -1,6 +1,6 @@
{ {
"major": 4, "major": 4,
"minor": 0, "minor": 0,
"sub": 10, "sub": 11,
"meta": "beta" "meta": "beta"
} }

View File

@ -40,6 +40,16 @@
<!-- Bootstrap Toggle --> <!-- Bootstrap Toggle -->
<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet"> <link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet">
<script defer src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script> <script defer src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"
integrity="sha512-ElRFoEQdI5Ht6kZvyzXhYG9NqjtkmlkfYk0wr6wHxU9JEHakS7UJZNeml5ALk+8IKlU6jDgMabC3vkumRokgJA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"
integrity="sha512-UXumZrZNiOwnTcZSHLOfcTs0aos2MzBWHXOHOuB0J/R44QB0dwY5JgfbvljXcklVf65Gc4El6RjZ+lnwd2az2g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/1.2.1/chartjs-plugin-zoom.min.js"
integrity="sha512-klQv6lz2YR+MecyFYMFRuU2eAl8IPRo6zHnsc9n142TJuJHS8CG0ix4Oq9na9ceeg1u5EkBfZsFcV3U7J51iew=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- End Bootstrap Toggle --> <!-- End Bootstrap Toggle -->
</head> </head>

View File

@ -193,9 +193,9 @@
<a data-id="{{server['server_data']['server_id']}}" class="" title="{{ <a data-id="{{server['server_data']['server_id']}}" class="" title="{{
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting', translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
data['lang']) }}</i></a> data['lang']) }}</i></a>
{% elif server['stats']['downloading']%} {% elif server['stats']['importing']%}
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i> <a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'downloading', {{ translate('serverTerm', 'importing',
data['lang']) }}</a> data['lang']) }}</a>
{% else %} {% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip" <a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
@ -399,12 +399,12 @@
data['lang']) }}</i></a> data['lang']) }}</i></a>
</div> </div>
</div> </div>
{% elif server['stats']['downloading']%} {% elif server['stats']['importing']%}
<div class="row"> <div class="row">
<div class="col-12 px-0"> <div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i <a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i
class="fa fa-spinner fa-spin"></i> class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'downloading', data['lang']) }}</a> {{ translate('serverTerm', 'importing', data['lang']) }}</a>
</div> </div>
</div> </div>
{% else %} {% else %}
@ -893,9 +893,6 @@
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>', message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data["lang"]) %} </div>',
closeButton: false, closeButton: false,
}); });
setTimeout(function () {
location.reload();
}, 5000)
} }
</script> </script>
<script src="/static/assets/vendors/js/jquery-ui.js"></script> <script src="/static/assets/vendors/js/jquery-ui.js"></script>

View File

@ -87,6 +87,38 @@
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td> <td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr> </tr>
{% end %} {% end %}
{% for user in data['managed_users'] %}
<tr>
<td><i class="fas fa-user"></i> {{ user.username }}</td>
<td>
{% if user.enabled %}
<span class="text-success">
<i class="fas fa-check-square"></i> Yes
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> No
</span>
{% end %}
</td>
<td id="server_list_{{user.user_id}}">
<ul id="{{user.user_id}}">
{% for item in data['auth-servers'][user.user_id] %}
<li>{{item}}</li>
{% end %}
</ul>
</td>
<td id="role_list_{{user.user_id}}">
<ul>
{% for item in data['user-roles'][user.user_id] %}
<li data-toggle="tooltip" title="{{ item }}">{{item}}</li>
{% end %}
</ul>
</td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -146,6 +178,34 @@
</tr> </tr>
{% end %} {% end %}
{% if not data['superuser'] %}
{% for role in data['managed_roles'] %}
{% if role.role_id not in data['assigned_roles'] %}
<tr>
<td>{{ role.role_name }}</td>
<td id="role_list_{{role.role_id}}">
<ul id="{{role.role_id}}">
{% for item in data['role-servers'][role.role_id] %}
<li>{{item}}</li>
{% end %}
</ul>
</td>
<td>
<ul>
{% for user in data['users'] %}
{% for ruser in data['user-roles'][user.user_id] %}
{% if ruser == role.role_name %}
<li>{{ user.username }}</li>
{% end %}
{% end %}
{% end %}
</ul>
</td>
<td><a href="/panel/edit_role?id={{role.role_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}
{% end %}
{% end %}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -49,7 +49,7 @@
</ul> </ul>
<div class=""> <div class="">
<div class=""> <div class="">
<form class="forms-sample" method="post" action="{{ '/panel/add_role' if data['new_role'] else '/panel/edit_role' }}"> <form id="role_form" class="forms-sample" method="post" action="{{ '/panel/add_role' if data['new_role'] else '/panel/edit_role' }}">
{% raw xsrf_form_html() %} {% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}"> <input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
<input type="hidden" name="subpage" value="config"> <input type="hidden" name="subpage" value="config">
@ -63,6 +63,29 @@
<label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</small> </label> <label for="role_name">{{ translate('rolesConfig', 'roleName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('rolesConfig', 'roleDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" > <input type="text" class="form-control" name="role_name" id="role_name" value="{{ data['role']['role_name'] }}" placeholder="Role Name" >
</div> </div>
<br />
{% if data['superuser'] %}
<div class="form-group">
<label class="form-label" for="manager">{{ translate('rolesConfig', 'selectManager',
data['lang']) }}
</label>
<select class="form-select form-control form-control-lg select-css" id="manager" name="manager"
form="role_form">
{% if data["role_manager"]["username"] != "None" %}
<option value='{{data["role_manager"]["user_id"]}}'>{{ data["role_manager"]["username"]
}}</option>
{% end %}
<option value="">None</option>
{% for user in data['users'] %}
{% if user.user_id != data['role_manager']['user_id']
%}
<option value="{{user.user_id}}">{{user.username}}</option>
{% end %}
{% end %}
</select>
</div>
{% end %}
</div> </div>
</div> </div>
@ -252,6 +275,8 @@
<br /> <br />
{{ translate('rolesConfig', 'configUpdate', data['lang']) }} {{ str(data['role']['last_update']) }} {{ translate('rolesConfig', 'configUpdate', data['lang']) }} {{ str(data['role']['last_update']) }}
<br /> <br />
{{ translate('userConfig', 'manager', data['lang']) }}: {{ data['role_manager']['username'] }}
<br />
</p> </p>
</blockquote> </blockquote>
<div class="text-center"> <div class="text-center">

View File

@ -121,6 +121,27 @@ data['lang']) }}{% end %}
{% end %} {% end %}
</select> </select>
</div> </div>
{% if data['superuser'] %}
<div class="form-group">
<label class="form-label" for="manager">{{ translate('userConfig', 'selectManager',
data['lang']) }}
</label>
<select class="form-select form-control form-control-lg select-css" id="manager" name="manager"
form="user_form">
{% if data["manager"]["username"] != "None" %}
<option value='{{data["manager"]["user_id"]}}'>{{ data["manager"]["username"]
}}</option>
{% end %}
<option value="">None</option>
{% for user in data['users'] %}
{% if user.username != data['user']['username'] and user.user_id != data['manager']['user_id']
%}
<option value="{{user.user_id}}">{{user.username}}</option>
{% end %}
{% end %}
</select>
</div>
{% end %}
</div> </div>
</div> </div>
@ -141,21 +162,29 @@ data['lang']) }}{% end %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for role in data['roles_all'] %} {% for role in data['roles'] %}
<tr> <tr>
{% if data['superuser'] or role.role_id in data['user']['roles'] or role.manager == data['exec_user'] %}
<td>{{ role.role_name }}</td> <td>{{ role.role_name }}</td>
<td> <td>
{% if role.role_id in data['user']['roles'] %} {% if role.role_id in data['user']['roles'] %}
{% if role.manager == data['exec_user'] or data['superuser'] %}
<input type="checkbox" class="form-check-input" <input type="checkbox" class="form-check-input"
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
checked="" value="1"> checked="" value="1">
{% else %} {% else %}
<input type="checkbox" class="form-check-input"
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
checked="" value="1" disabled>
{% end %}
{% elif data['superuser'] or role.manager == data['exec_user'] %}
<input type="checkbox" class="form-check-input" <input type="checkbox" class="form-check-input"
id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership" id="role_{{ role.role_id }}_membership" name="role_{{ role.role_id }}_membership"
value="1"> value="1">
{% end %} {% end %}
</td> </td>
{% end %}
</tr> </tr>
{% end %} {% end %}
</tbody> </tbody>
@ -268,6 +297,8 @@ data['lang']) }}{% end %}
<br /> <br />
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }} {{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
<br /> <br />
{{ translate('userConfig', 'manager', data['lang'])}}: {{data['manager']['username'] }}
<br />
</p> </p>
</blockquote> </blockquote>
</div> </div>
@ -284,7 +315,7 @@ data['lang']) }}{% end %}
{% else %} {% else %}
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{ <button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{
translate('userConfig', 'deleteUserB', data['lang']) }}</a> translate('userConfig', 'deleteUserB', data['lang']) }}</a>
{% end %} {% end %}</button>
</div> </div>
</div> </div>

View File

@ -30,6 +30,7 @@
{% if data['permissions']['Players'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock' %} {% if data['permissions']['Players'] in data['user_permissions'] and data['server_data']['type'] != 'minecraft-bedrock' %}
<a class="dropdown-item {% if data['active_link'] == 'admin_controls' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true"><i class="fas fa-users"></i> {{ translate('serverDetails', 'playerControls', data['lang']) }}</a> <a class="dropdown-item {% if data['active_link'] == 'admin_controls' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=admin_controls" role="tab" aria-selected="true"><i class="fas fa-users"></i> {{ translate('serverDetails', 'playerControls', data['lang']) }}</a>
{% end %} {% end %}
<a class="dropdown-item {% if data['active_link'] == 'metrics' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=metrics" role="tab" aria-selected="true"><i class='fas fa-chart-area'></i>{{ translate('serverDetails', 'metrics', data['lang']) }}</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -49,4 +49,8 @@
<i class="fas fa-users"></i>{{ translate('serverDetails', 'playerControls', data['lang']) }}</a> <i class="fas fa-users"></i>{{ translate('serverDetails', 'playerControls', data['lang']) }}</a>
</li> </li>
{% end %} {% end %}
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'metrics' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=metrics" role="tab" aria-selected="true">
<i class='fas fa-chart-area'></i>{{ translate('serverDetails', 'metrics', data['lang']) }}</a>
</li>
</ul> </ul>

View File

@ -165,6 +165,18 @@
</div> </div>
{% end %} {% end %}
<div class="form-group">
<label for="shutdown_timeout">{{ translate('serverConfig', 'shutdownTimeout', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'timeoutExplain1', data['lang'])
}}&nbsp;
{{ data['server_stats']['server_id']['stop_command'] }}&nbsp;{{ translate('serverConfig',
'timeoutExplain2', data['lang']) }}
</small> </label>
<input type="number" class="form-control" name="shutdown_timeout" id="shutdown_timeout"
value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60"
required>
</div>
<div class="form-group"> <div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc',

View File

@ -0,0 +1,225 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<span class="d-none d-sm-block">
{% include "parts/server_controls_list.html %}
</span>
<span class="d-block d-sm-none">
{% include "parts/m_server_controls_list.html %}
</span>
<button style="float: right; visibility: hidden;" class="btn btn-outline-success reset-button"
id="reset-button"><i class="fas fa-undo"></i>&nbsp;{{ translate('serverMetrics', 'resetZoom', data['lang'])
}}</button>
{% if data['user_data']['hints'] %}
<span data-html="true" class="hints text-center" title="<i class='fa fa-info-circle'></i> " ,
data-content="{{
translate('serverMetrics', 'zoomHint1' , data['lang'])}} <br> <br> {{ translate('serverMetrics', 'zoomHint2', data['lang'])}}" , data-placement="top"></span>
{% end %}
<div class="chart-wrapper">
<canvas id="lineChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.chart-wrapper {
height: 65vh;
}
.popover-body {
color: white !important;
;
}
</style>
<script type="text/javascript">
var zoomed = false;
//line
var ctxL = document.getElementById("lineChart").getContext('2d');
const players = []
const dates = []
const ram = []
const cpu = []
{% for item in data['history_stats'] %}
{% if 'minecraft-java' in data['server_stats']['server_type'] %}
players.push("{{ item.online }}");
{% end %}
dates.push("{{ item.created.strftime('%Y/%m/%d, %H:%M:%S') }}");
ram.push("{{ item.mem_percent }}")
cpu.push("{{ item.cpu }}")
{% end %}
var hist_chart = new Chart(ctxL, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: "Players",
data: players,
borderColor: [
'rgba(136, 98, 224, .5)',
],
borderWidth: 2,
lineTension: 0,
},
{
label: "MEM",
data: ram,
borderColor: [
'rgba(33, 150, 243, .5)',
],
borderWidth: 2,
lineTension: 0,
},
{
label: "CPU",
data: cpu,
borderColor: [
'rgba(255, 175, 0, .5)',
],
borderWidth: 2,
lineTension: 0,
},
]
},
options: {
maintainAspectRatio: false,
plugins: {
zoom: {
zoom: {
onZoom({ hist_chart }) {
console.log("zooming");
document.getElementById("reset-button").style.visibility = "visible";
zoomed = true;
},
wheel: {
enabled: true,
modifierKey: 'shift',
},
drag: {
enabled: true,
modifierKey: "shift"
},
pinch: {
enabled: true
},
mode: 'x',
},
pan: {
enabled: true,
mode: "x",
threshhold: 1,
}
},
},
fill: false,
lineTension: 5,
responsive: true,
scales: {
y: {
min: 0,
},
x: {
position: 'right',
min: dates.length - 200,
}
}
}
});
$(window).ready(function () {
$('body').click(function () {
$('.hints').popover("hide");
});
});
$(document).ready(function () {
if ($(window).width() > 1000) {
$('[data-toggle="popover"]').popover();
$('.hints').popover("show");
}
webSocket.on('update_server_details', function (data) {
dates.push(data.created);
players.push(data.online);
cpu.push(data.cpu)
ram.push(data.mem_percent)
data = {
labels: dates,
datasets: [{
label: "Players",
data: players,
borderColor: [
'rgba(136, 98, 224, .5)',
],
borderWidth: 2,
lineTension: 0,
},
{
label: "MEM",
data: ram,
borderColor: [
'rgba(33, 150, 243, .5)',
],
borderWidth: 2,
lineTension: 0,
},
{
label: "CPU",
data: cpu,
borderColor: [
'rgba(255, 175, 0, .5)',
],
borderWidth: 2,
lineTension: 0,
},
]
}
if (!zoomed) {
hist_chart.options.scales.x.min = dates.length - 200;
}
hist_chart.update(data)
});
$(".reset-button").click(function () {
console.log("resetting zoom");
zoomed = false;
hist_chart.resetZoom();
document.getElementById("reset-button").style.visibility = "hidden";
});
});
</script>
{% end %}

View File

@ -65,9 +65,9 @@
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div> </div>
{% elif data['downloading'] %} {% elif data['importing'] %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'downloading', <button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'importing',
data['lang']) }}</button> data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("roles", manager=peewee.IntegerField(null=True))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("roles", ["manager"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("users", manager=peewee.IntegerField(null=True))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("users", ["manager"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("servers", created_by=peewee.IntegerField(default=-100))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("servers", ["created_by"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,20 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.drop_columns("user_crafty", ["created_server"])
migrator.drop_columns("user_crafty", ["created_user"])
migrator.drop_columns("user_crafty", ["created_role"])
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
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 rollback migrations here.
"""

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("servers", shutdown_timeout=peewee.IntegerField(default=60))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("servers", ["shutdown_timeout"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,17 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.rename_column("server_stats", "downloading", "importing")
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.rename_column("server_stats", "importing", "downloading")
"""
Write your rollback migrations here.
"""

View File

@ -245,7 +245,8 @@
"roleUsers": "Role Users: ", "roleUsers": "Role Users: ",
"serverAccess": "Access?", "serverAccess": "Access?",
"serverName": "Server Name", "serverName": "Server Name",
"serversDesc": "servers this role is allowed to access" "serversDesc": "servers this role is allowed to access",
"selectManager": "Select a manager for this Role"
}, },
"serverBackups": { "serverBackups": {
"backupAtMidnight": "Auto-backup at midnight?", "backupAtMidnight": "Auto-backup at midnight?",
@ -324,7 +325,10 @@
"stopBeforeDeleting": "Please stop the server before deleting it", "stopBeforeDeleting": "Please stop the server before deleting it",
"update": "Update Executable", "update": "Update Executable",
"yesDelete": "Yes, delete", "yesDelete": "Yes, delete",
"yesDeleteFiles": "Yes, delete files" "yesDeleteFiles": "Yes, delete files",
"shutdownTimeout": "Shutdown Timeout",
"timeoutExplain1": "How long Crafty will wait for your server to shutdown after executing the",
"timeoutExplain2": "command before it forces the process down."
}, },
"serverConfigHelp": { "serverConfigHelp": {
"desc": "Here is where you can change the configuration of your server", "desc": "Here is where you can change the configuration of your server",
@ -351,7 +355,8 @@
"playerControls": "Player Management", "playerControls": "Player Management",
"schedule": "Schedule", "schedule": "Schedule",
"serverDetails": "Server Details", "serverDetails": "Server Details",
"terminal": "Terminal" "terminal": "Terminal",
"metrics": "Metrics"
}, },
"serverFiles": { "serverFiles": {
"clickUpload": "Click here to select your files", "clickUpload": "Click here to select your files",
@ -463,7 +468,7 @@
"serverTerm": { "serverTerm": {
"commandInput": "Enter your command", "commandInput": "Enter your command",
"delay-explained": "The service/agent has recently started and is delaying the start of the minecraft server instance", "delay-explained": "The service/agent has recently started and is delaying the start of the minecraft server instance",
"downloading": "Downloading...", "importing": "Importing...",
"restart": "Restart", "restart": "Restart",
"sendCommand": "Send command", "sendCommand": "Send command",
"start": "Start", "start": "Start",
@ -472,6 +477,11 @@
"stopScroll": "Stop Auto Scrolling", "stopScroll": "Stop Auto Scrolling",
"updating": "Updating..." "updating": "Updating..."
}, },
"serverMetrics": {
"resetZoom": "Reset Zoom",
"zoomHint1": "To zoom on the graph hold your shift key then use your scroll wheel.",
"zoomHint2": "Alternatively hold the shift key then click and drag the area you'd like to zoom in on."
},
"serverWizard": { "serverWizard": {
"absoluteServerPath": "Absolute path to your server", "absoluteServerPath": "Absolute path to your server",
"absoluteZipPath": "Absolute path to your server", "absoluteZipPath": "Absolute path to your server",
@ -556,6 +566,8 @@
"userRoles": "User Roles", "userRoles": "User Roles",
"userRolesDesc": "Roles this user is a member of.", "userRolesDesc": "Roles this user is a member of.",
"userSettings": "User Settings", "userSettings": "User Settings",
"uses": "Number of uses allowed (-1==No Limit)" "uses": "Number of uses allowed (-1==No Limit)",
"manager": "Manager",
"selectManager": "Select Manager for User"
} }
} }

View File

@ -14,6 +14,7 @@ from app.classes.shared.import3 import Import3
from app.classes.shared.console import Console from app.classes.shared.console import Console
from app.classes.shared.helpers import Helpers from app.classes.shared.helpers import Helpers
from app.classes.models.users import HelperUsers from app.classes.models.users import HelperUsers
from app.classes.shared.import_helper import ImportHelpers
console = Console() console = Console()
helper = Helpers() helper = Helpers()
@ -135,8 +136,9 @@ if __name__ == "__main__":
else: else:
Console.debug("Existing install detected") Console.debug("Existing install detected")
file_helper = FileHelpers(helper) file_helper = FileHelpers(helper)
import_helper = ImportHelpers(helper, file_helper)
# now the tables are created, we can load the tasks_manager and server controller # now the tables are created, we can load the tasks_manager and server controller
controller = Controller(database, helper, file_helper) controller = Controller(database, helper, file_helper, import_helper)
import3 = Import3(helper, controller) import3 = Import3(helper, controller)
tasks_manager = TasksManager(helper, controller) tasks_manager = TasksManager(helper, controller)
tasks_manager.start_webserver() tasks_manager.start_webserver()