This commit is contained in:
xithical 2022-01-21 23:09:04 -06:00
commit 5adfc613d8
70 changed files with 3385 additions and 2078 deletions

View File

@ -117,7 +117,7 @@ win-dev-build:
--paths .venv\Lib\site-packages
--hidden-import cryptography
--hidden-import cffi
--collect-all apscheduler
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz
@ -158,7 +158,7 @@ win-prod-build:
--paths .venv\Lib\site-packages
--hidden-import cryptography
--hidden-import cffi
--collect-all apscheduler
--hidden-import apscheduler
--collect-all tzlocal
--collect-all tzdata
--collect-all pytz

View File

@ -18,6 +18,7 @@ from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__)
@ -70,3 +71,7 @@ class Crafty_Perms_Controller:
@staticmethod
def add_server_creation(user_id):
return crafty_permissions.add_server_creation(user_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys):
return crafty_permissions.get_api_key_permissions_list(key)

View File

@ -31,10 +31,6 @@ class Management_Controller:
def get_latest_hosts_stats():
return management_helper.get_latest_hosts_stats()
@staticmethod
def new_api_token():
return management_helper.new_api_token()
#************************************************************************************************
# Commands Methods
#************************************************************************************************
@ -44,13 +40,11 @@ class Management_Controller:
@staticmethod
def send_command(user_id, server_id, remote_ip, command):
server_name = servers_helper.get_server_friendly_name(server_id)
# Example: Admin issued command start_server for server Survival
management_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name),
server_id, remote_ip)
management_helper.add_command(server_id, user_id, remote_ip, command)
@staticmethod

View File

@ -39,7 +39,9 @@ class Roles_Controller:
@staticmethod
def update_role(role_id, role_data={}, permissions_mask="00000000"):
def update_role(role_id: str, role_data = None, permissions_mask: str = "00000000"):
if role_data is None:
role_data = {}
base_data = Roles_Controller.get_role_with_servers(role_id)
up_data = {}
added_servers = set()

View File

@ -14,7 +14,7 @@ from app.classes.shared.console import console
from app.classes.shared.main_models import db_helper
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.models.users import users_helper
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.roles import roles_helper
from app.classes.models.servers import servers_helper
@ -42,11 +42,6 @@ class Server_Perms_Controller:
permissions_list = server_permissions.get_role_permissions_list(role_id)
return permissions_list
@staticmethod
def get_server_permissions_foruser(user_id, server_id):
permissions_list = server_permissions.get_user_permissions_list(user_id, server_id)
return permissions_list
@staticmethod
def add_role_server(server_id, role_id, rs_permissions="00000000"):
return server_permissions.add_role_server(server_id, role_id, rs_permissions)
@ -78,8 +73,30 @@ class Server_Perms_Controller:
return server_permissions.get_role_permissions_list(role_id)
@staticmethod
def get_user_permissions_list(user_id, server_id):
return server_permissions.get_user_permissions_list(user_id, server_id)
def get_user_id_permissions_list(user_id: str, server_id: str):
return server_permissions.get_user_id_permissions_list(user_id, server_id)
@staticmethod
def get_api_key_id_permissions_list(key_id: str, server_id: str):
key = users_helper.get_user_api_key(key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_user_id_permissions_list(user_id: str, server_id: str):
return server_permissions.get_user_id_permissions_list(user_id, server_id)
@staticmethod
def get_api_key_id_permissions_list(key_id: str, server_id: str):
key = users_helper.get_user_api_key(key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_authorized_servers_stats_from_roles(user_id):

View File

@ -17,7 +17,7 @@ from app.classes.shared.console import console
from app.classes.shared.main_models import db_helper
from app.classes.models.servers import servers_helper
from app.classes.models.roles import roles_helper
from app.classes.models.users import users_helper
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.shared.server import Server
@ -81,6 +81,22 @@ class Servers_Controller:
def get_all_servers_stats():
return servers_helper.get_all_servers_stats()
@staticmethod
def get_authorized_servers_stats_api_key(api_key: ApiKeys):
server_data = []
authorized_servers = Servers_Controller.get_authorized_servers(api_key.user.user_id)
for s in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
key_permissions = server_permissions.get_api_key_permissions_list(api_key, s.get('server_id'))
if Enum_Permissions_Server.Commands in key_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0],
"user_command_permission": user_command_permission})
return server_data
@staticmethod
def get_authorized_servers_stats(user_id):
server_data = []
@ -88,12 +104,18 @@ class Servers_Controller:
for s in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
user_permissions = server_permissions.get_user_permissions_list(user_id, s.get('server_id'))
# TODO
user_permissions = server_permissions.get_user_id_permissions_list(user_id, s.get('server_id'))
if Enum_Permissions_Server.Commands in user_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":user_command_permission})
server_data.append({
'server_data': s,
'stats': db_helper.return_rows(latest)[0],
'user_command_permission': user_command_permission
})
return server_data
@staticmethod
@ -112,17 +134,20 @@ class Servers_Controller:
return servers_helper.server_id_exists(server_id)
@staticmethod
def server_id_authorized(serverId, user_id):
authorized = 0
def server_id_authorized(server_id_a, user_id):
user_roles = users_helper.user_role_query(user_id)
for role in user_roles:
authorized = server_permissions.get_role_servers_from_role_id(role.role_id)
for server_id_b in server_permissions.get_role_servers_from_role_id(role.role_id):
if str(server_id_a) == str(server_id_b.server_id):
return True
return False
#authorized = db_helper.return_rows(authorized)
if authorized.count() == 0:
return False
return True
@staticmethod
def server_id_authorized_api_key(server_id: str, api_key: ApiKeys) -> bool:
# TODO
return Servers_Controller.server_id_authorized(server_id, api_key.user.user_id)
# There is no view server permission
# permission_helper.both_have_perm(api_key)
@staticmethod
def set_update(server_id, value):

View File

@ -2,6 +2,8 @@ import os
import time
import logging
import sys
from typing import Optional
import yaml
import asyncio
import shutil
@ -13,6 +15,7 @@ from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.users import Users, users_helper
from app.classes.shared.authentication import authentication
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
from app.classes.models.management import management_helper
@ -31,10 +34,6 @@ class Users_Controller:
def get_id_by_name(username):
return users_helper.get_user_id_by_name(username)
@staticmethod
def get_user_by_api_token(token: str):
return users_helper.get_user_by_api_token(token)
@staticmethod
def get_user_lang_by_id(user_id):
return users_helper.get_user_lang_by_id(user_id)
@ -52,7 +51,11 @@ class Users_Controller:
users_helper.set_support_path(user_id, support_path)
@staticmethod
def update_user(user_id, user_data={}, user_crafty_data={}):
def update_user(user_id: str, user_data=None, user_crafty_data=None):
if user_crafty_data is None:
user_crafty_data = {}
if user_data is None:
user_data = {}
base_data = users_helper.get_user(user_id)
up_data = {}
added_roles = set()
@ -64,9 +67,6 @@ class Users_Controller:
elif key == "roles":
added_roles = user_data['roles'].difference(base_data['roles'])
removed_roles = base_data['roles'].difference(user_data['roles'])
elif key == "regen_api":
if user_data['regen_api']:
up_data['api_token'] = management_helper.new_api_token()
elif key == "password":
if user_data['password'] is not None and user_data['password'] != "":
up_data['password'] = helper.encode_pass(user_data['password'])
@ -77,13 +77,12 @@ class Users_Controller:
logger.debug("user: {} +role:{} -role:{}".format(user_data, added_roles, removed_roles))
for role in added_roles:
users_helper.get_or_create(user_id=user_id, role_id=role)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
permissions_mask = user_crafty_data.get('permissions_mask', '000')
if 'server_quantity' in user_crafty_data:
limit_server_creation = user_crafty_data['server_quantity'][
Enum_Permissions_Crafty.Server_Creation.name]
for key in user_crafty_data:
if key == "permissions_mask":
permissions_mask = user_crafty_data['permissions_mask']
if key == "server_quantity":
limit_server_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Server_Creation.name]
limit_user_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.User_Config.name]
limit_role_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Roles_Config.name]
else:
@ -98,8 +97,8 @@ class Users_Controller:
users_helper.update_user(user_id, up_data)
@staticmethod
def add_user(username, password=None, email="default@example.com", api_token=None, enabled=True, superuser=False):
return users_helper.add_user(username, password=password, email=email, api_token=api_token, enabled=enabled, superuser=superuser)
def add_user(username, password=None, email="default@example.com", enabled: bool = True, superuser: bool = False):
return users_helper.add_user(username, password=password, email=email, enabled=enabled, superuser=superuser)
@staticmethod
def remove_user(user_id):
@ -109,9 +108,19 @@ class Users_Controller:
def user_id_exists(user_id):
return users_helper.user_id_exists(user_id)
#************************************************************************************************
@staticmethod
def get_user_id_by_api_token(token: str) -> str:
token_data = authentication.check_no_iat(token)
return token_data['user_id']
@staticmethod
def get_user_by_api_token(token: str):
_, user = authentication.check(token)
return user
# ************************************************************************************************
# User Roles Methods
#************************************************************************************************
# ************************************************************************************************
@staticmethod
def get_user_roles_id(user_id):
@ -132,3 +141,29 @@ class Users_Controller:
@staticmethod
def user_role_query(user_id):
return users_helper.user_role_query(user_id)
# ************************************************************************************************
# Api Keys Methods
# ************************************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
return users_helper.get_user_api_keys(user_id)
@staticmethod
def get_user_api_key(key_id: str):
return users_helper.get_user_api_key(key_id)
@staticmethod
def add_user_api_key(name: str, user_id: str, superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None):
return users_helper.add_user_api_key(name, user_id, superuser, server_permissions_mask, crafty_permissions_mask)
@staticmethod
def delete_user_api_keys(user_id: str):
return users_helper.delete_user_api_keys(user_id)
@staticmethod
def delete_user_api_key(key_id: str):
return users_helper.delete_user_api_key(key_id)

View File

@ -12,6 +12,7 @@ from app.classes.shared.console import console
from app.classes.models.servers import Servers
from app.classes.minecraft.server_props import ServerProps
from app.classes.web.websocket_helper import websocket_helper
from app.classes.models.server_permissions import server_permissions
logger = logging.getLogger(__name__)
@ -190,7 +191,6 @@ class ServerJars:
except Exception as e:
logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e))
pass
websocket_helper.broadcast('notification', "Executable download finished for server named: " + name)
return False

View File

@ -5,7 +5,8 @@ import datetime
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.users import Users
from app.classes.models.users import Users, ApiKeys
from app.classes.shared.permission_helper import permission_helper
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
@ -191,4 +192,18 @@ class Permissions_Crafty:
User_Crafty.save(user_crafty)
return user_crafty.created_server
@staticmethod
def get_api_key_permissions_list(key: ApiKeys):
user = key.user
if user.superuser and key.superuser:
return crafty_permissions.get_permissions_list()
else:
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(user.user_id)
key_permissions_mask: str = key.crafty_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_list = crafty_permissions.get_permissions(permissions_mask)
return permissions_list
crafty_permissions = Permissions_Crafty()

View File

@ -9,6 +9,8 @@ from app.classes.shared.main_models import db_helper
from app.classes.models.users import Users, users_helper
from app.classes.models.servers import Servers, servers_helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.models.server_permissions import server_permissions
import time
logger = logging.getLogger(__name__)
@ -156,7 +158,7 @@ class helpers_management:
@staticmethod
def get_unactioned_commands():
query = Commands.select().where(Commands.executed == 0)
return db_helper.return_rows(query)
return query
@staticmethod
def mark_command_complete(command_id=None):
@ -181,7 +183,9 @@ class helpers_management:
audit_msg = "{} {}".format(str(user_data['username']).capitalize(), log_msg)
websocket_helper.broadcast('notification', audit_msg)
server_users = server_permissions.get_server_user_list(server_id)
for user in server_users:
websocket_helper.broadcast_user(user,'notification', audit_msg)
Audit_Log.insert({
Audit_Log.user_name: user_data['username'],

View File

@ -7,7 +7,8 @@ from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.servers import Servers
from app.classes.models.roles import Roles
from app.classes.models.users import users_helper
from app.classes.models.users import User_Roles, users_helper, ApiKeys, Users
from app.classes.shared.permission_helper import permission_helper
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
@ -78,10 +79,7 @@ class Permissions_Servers:
@staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server):
result = False
if permission_mask[permission_tested.value] == '1':
result = True
return result
return permission_mask[permission_tested.value] == '1'
@staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
@ -94,6 +92,14 @@ class Permissions_Servers:
def get_permission(permission_mask, permission_tested: Enum_Permissions_Server):
return permission_mask[permission_tested.value]
@staticmethod
def get_token_permissions(permissions_mask, api_permissions_mask):
permissions_list = []
for member in Enum_Permissions_Server.__members__.items():
if permission_helper.both_have_perm(permissions_mask, api_permissions_mask, member[1]):
permissions_list.append(member[1])
return permissions_list
#************************************************************************************************
# Role_Servers Methods
@ -146,7 +152,9 @@ class Permissions_Servers:
Role_Servers.save(role_server)
@staticmethod
def delete_roles_permissions(role_id, removed_servers={}):
def delete_roles_permissions(role_id, removed_servers=None):
if removed_servers is None:
removed_servers = {}
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
@staticmethod
@ -155,21 +163,67 @@ class Permissions_Servers:
return Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
@staticmethod
def get_user_permissions_list(user_id, server_id):
permissions_mask = ''
permissions_list = []
def get_user_id_permissions_mask(user_id, server_id: str):
user = users_helper.get_user_model(user_id)
return server_permissions.get_user_permissions_mask(user, server_id)
user = users_helper.get_user(user_id)
if user['superuser'] == True:
@staticmethod
def get_user_permissions_mask(user: Users, server_id: str):
if user.superuser:
permissions_mask = '1' * len(server_permissions.get_permissions_list())
else:
roles_list = users_helper.get_user_roles_id(user.user_id)
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
permissions_mask = role_server[0].permissions
return permissions_mask
@staticmethod
def get_server_user_list(server_id):
final_users = []
server_roles = Role_Servers.select().where(Role_Servers.server_id == server_id)
super_users = Users.select().where(Users.superuser == True)
for role in server_roles:
users = User_Roles.select().where(User_Roles.role_id == role.role_id)
for user in users:
if user.user_id.user_id not in final_users:
final_users.append(user.user_id.user_id)
for suser in super_users:
if suser.user_id not in final_users:
final_users.append(suser.user_id)
return final_users
@staticmethod
def get_user_id_permissions_list(user_id, server_id: str):
user = users_helper.get_user_model(user_id)
return server_permissions.get_user_permissions_list(user, server_id)
@staticmethod
def get_user_permissions_list(user: Users, server_id: str):
if user.superuser:
permissions_list = server_permissions.get_permissions_list()
else:
roles_list = users_helper.get_user_roles_id(user_id)
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == int(server_id)).execute()
if len(role_server) > 0:
permissions_mask = role_server[0].permissions
else:
permissions_mask = '00000000'
permissions_mask = server_permissions.get_user_permissions_mask(user, server_id)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list
server_permissions = Permissions_Servers()
@staticmethod
def get_api_key_id_permissions_list(key_id, server_id: str):
key = ApiKeys.get(ApiKeys.token_id == key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
user = key.user
if user.superuser and key.superuser:
return server_permissions.get_permissions_list()
else:
roles_list = users_helper.get_user_roles_id(user['user_id'])
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
user_permissions_mask = role_server[0].permissions
key_permissions_mask = key.server_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list
server_permissions = Permissions_Servers()

View File

@ -2,6 +2,7 @@ import os
import sys
import logging
import datetime
from typing import Optional, List, Union
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
@ -41,14 +42,32 @@ class Users(Model):
email = CharField(default="default@example.com")
enabled = BooleanField(default=True)
superuser = BooleanField(default=False)
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
lang = CharField(default="en_EN")
support_logs = CharField(default = '')
valid_tokens_from = DateTimeField(default=datetime.datetime.now)
class Meta:
table_name = "users"
database = database
# ************************************************************************************************
# API Keys Class
# ************************************************************************************************
class ApiKeys(Model):
token_id = AutoField()
name = CharField(default='', unique=True, index=True)
created = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(Users, backref='api_token', index=True)
server_permissions = CharField(default='00000000')
crafty_permissions = CharField(default='000')
superuser = BooleanField(default=False)
class Meta:
table_name = 'api_keys'
database = database
#************************************************************************************************
# User Roles Class
#************************************************************************************************
@ -86,18 +105,6 @@ class helper_users:
except DoesNotExist:
return None
@staticmethod
def get_user_by_api_token(token: str):
query = Users.select().where(Users.api_token == token)
if query.exists():
user = model_to_dict(Users.get(Users.api_token == token))
# I know it should apply it without setting it but I'm just making sure
user = users_helper.add_user_roles(user)
return user
else:
return {}
@staticmethod
def user_query(user_id):
user_query = Users.select().where(Users.user_id == user_id)
@ -117,7 +124,6 @@ class helper_users:
'email': "default@example.com",
'enabled': True,
'superuser': True,
'api_token': None,
'roles': [],
'servers': [],
'support_logs': '',
@ -140,21 +146,21 @@ class helper_users:
return False
@staticmethod
def add_user(username, password=None, email=None, api_token=None, enabled=True, superuser=False):
def get_user_model(user_id: str) -> Users:
user = Users.get(Users.user_id == user_id)
user = users_helper.add_user_roles(user)
return user
@staticmethod
def add_user(username: str, password: Optional[str] = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str:
if password is not None:
pw_enc = helper.encode_pass(password)
else:
pw_enc = None
if api_token is None:
api_token = users_helper.new_api_token()
else:
if type(api_token) is not str and len(api_token) != 32:
raise ValueError("API token must be a 32 character string")
user_id = Users.insert({
Users.username: username.lower(),
Users.password: pw_enc,
Users.email: email,
Users.api_token: api_token,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string()
@ -162,10 +168,21 @@ class helper_users:
return user_id
@staticmethod
def update_user(user_id, up_data={}):
def update_user(user_id, up_data=None):
if up_data is None:
up_data = {}
if up_data:
Users.update(up_data).where(Users.user_id == user_id).execute()
@staticmethod
def get_super_user_list():
final_users = []
super_users = Users.select().where(Users.superuser == True)
for suser in super_users:
if suser.user_id not in final_users:
final_users.append(suser.user_id)
return final_users
@staticmethod
def remove_user(user_id):
with database.atomic():
@ -183,14 +200,6 @@ class helper_users:
return False
return True
@staticmethod
def new_api_token():
while True:
token = helper.random_string_generator(32)
test = list(Users.select(Users.user_id).where(Users.api_token == token))
if len(test) == 0:
return token
#************************************************************************************************
# User_Roles Methods
#************************************************************************************************
@ -223,7 +232,7 @@ class helper_users:
}).execute()
@staticmethod
def add_user_roles(user):
def add_user_roles(user: Union[dict, Users]):
if type(user) == dict:
user_id = user['user_id']
else:
@ -237,7 +246,11 @@ class helper_users:
for r in roles_query:
roles.add(r.role_id.role_id)
user['roles'] = roles
if type(user) == dict:
user['roles'] = roles
else:
user.roles = roles
#logger.debug("user: ({}) {}".format(user_id, user))
return user
@ -257,5 +270,36 @@ class helper_users:
def remove_roles_from_role_id(role_id):
User_Roles.delete().where(User_Roles.role_id == role_id).execute()
# ************************************************************************************************
# ApiKeys Methods
# ************************************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
return ApiKeys.select().where(ApiKeys.user_id == user_id).execute()
@staticmethod
def get_user_api_key(key_id: str) -> ApiKeys:
return ApiKeys.get(ApiKeys.token_id == key_id)
@staticmethod
def add_user_api_key(name: str, user_id: str, superuser: bool = False, server_permissions_mask: Optional[str] = None, crafty_permissions_mask: Optional[str] = None):
return ApiKeys.insert({
ApiKeys.name: name,
ApiKeys.user_id: user_id,
**({ApiKeys.server_permissions: server_permissions_mask} if server_permissions_mask is not None else {}),
**({ApiKeys.crafty_permissions: crafty_permissions_mask} if crafty_permissions_mask is not None else {}),
ApiKeys.superuser: superuser
}).execute()
@staticmethod
def delete_user_api_keys(user_id: str):
ApiKeys.delete().where(ApiKeys.user_id == user_id).execute()
@staticmethod
def delete_user_api_key(key_id: str):
ApiKeys.delete().where(ApiKeys.token_id == key_id).execute()
users_helper = helper_users()

View File

@ -0,0 +1,76 @@
import logging
import time
from typing import Optional, Dict, Any, Tuple
import jwt
from jwt import PyJWTError
from app.classes.models.users import users_helper, ApiKeys
from app.classes.shared.helpers import helper
logger = logging.getLogger(__name__)
class Authentication:
def __init__(self):
self.secret = "my secret"
self.secret = helper.get_setting('apikey_secret', None)
if self.secret is None or self.secret == 'random':
self.secret = helper.random_string_generator(64)
@staticmethod
def generate(user_id, extra=None):
if extra is None:
extra = {}
return jwt.encode(
{
'user_id': user_id,
'iat': int(time.time()),
**extra
},
authentication.secret,
algorithm="HS256"
)
@staticmethod
def read(token):
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
@staticmethod
def check_no_iat(token) -> Optional[Dict[str, Any]]:
try:
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error)
return None
@staticmethod
def check(token) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
try:
data = jwt.decode(token, authentication.secret, algorithms=["HS256"])
except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error)
return None
iat: int = data['iat']
key: Optional[ApiKeys] = None
if 'token_id' in data:
key_id = data['token_id']
key = users_helper.get_user_api_key(key_id)
if key is None:
return None
user_id: str = data['user_id']
user = users_helper.get_user(user_id)
# TODO: Have a cache or something so we don't constantly have to query the database
if int(user.get('valid_tokens_from').timestamp()) < iat:
# Success!
return key, data, user
else:
return None
@staticmethod
def check_bool(token) -> bool:
return authentication.check(token) is not None
authentication = Authentication()

View File

@ -41,29 +41,33 @@ class MainPrompt(cmd.Cmd, object):
self.universal_exit()
def do_migrations(self, line):
if (line == 'up'):
if line == 'up':
self.migration_manager.up()
elif (line == 'down'):
elif line == 'down':
self.migration_manager.down()
elif (line == 'done'):
elif line == 'done':
console.info(self.migration_manager.done)
elif (line == 'todo'):
elif line == 'todo':
console.info(self.migration_manager.todo)
elif (line == 'diff'):
elif line == 'diff':
console.info(self.migration_manager.diff)
elif (line == 'info'):
elif line == 'info':
console.info('Done: {}'.format(self.migration_manager.done))
console.info('FS: {}'.format(self.migration_manager.todo))
console.info('Todo: {}'.format(self.migration_manager.diff))
elif (line.startswith('add ')):
elif line.startswith('add '):
migration_name = line[len('add '):]
self.migration_manager.create(migration_name, False)
else:
console.info('Unknown migration command')
def do_threads(self, line):
@staticmethod
def do_threads(_line):
for thread in threading.enumerate():
print(f'Name: {thread.name} IDENT: {thread.ident}')
if sys.version_info >= (3, 8):
print(f'Name: {thread.name} Identifier: {thread.ident} TID/PID: {thread.native_id}')
else:
print(f'Name: {thread.name} Identifier: {thread.ident}')
def universal_exit(self):
logger.info("Stopping all server daemons / threads")
@ -75,11 +79,10 @@ class MainPrompt(cmd.Cmd, object):
sys.exit(0)
time.sleep(1)
@staticmethod
def help_exit():
console.help("Stops the server if running, Exits the program")
@staticmethod
def help_migrations():
console.help("Only for advanced users. Use with caution")

View File

@ -164,25 +164,6 @@ class Helpers:
cmd_out[ci] += c
return cmd_out
def check_for_old_logs(self, db_helper):
servers = db_helper.get_all_defined_servers()
for server in servers:
logs_path = os.path.split(server['log_path'])[0]
latest_log_file = os.path.split(server['log_path'])[1]
logs_delete_after = int(server['logs_delete_after'])
if logs_delete_after == 0:
continue
log_files = list(filter(
lambda val: val != latest_log_file,
os.listdir(logs_path)
))
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if self.check_file_exists(log_file_path) and \
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
os.remove(log_file_path)
def get_setting(self, key, default_return=False):
try:
@ -272,11 +253,13 @@ class Helpers:
(r'(\[.+?/INFO\])', r'<span class="mc-log-info">\1</span>'),
(r'(\[.+?/WARN\])', r'<span class="mc-log-warn">\1</span>'),
(r'(\[.+?/ERROR\])', r'<span class="mc-log-error">\1</span>'),
(r'(\[.+?/FATAL\])', r'<span class="mc-log-fatal">\1</span>'),
(r'(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])', r'<span class="mc-log-keyword">\1</span>'),
(r'\[(\d\d:\d\d:\d\d)\]', r'<span class="mc-log-time">[\1]</span>'),
(r'(\[.+? INFO\])', r'<span class="mc-log-info">\1</span>'),
(r'(\[.+? WARN\])', r'<span class="mc-log-warn">\1</span>'),
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>')
(r'(\[.+? ERROR\])', r'<span class="mc-log-error">\1</span>'),
(r'(\[.+? FATAL\])', r'<span class="mc-log-fatal">\1</span>')
]
# highlight users keywords

View File

@ -3,6 +3,8 @@ import pathlib
import time
import logging
import sys
from typing import Union
from app.classes.models.server_permissions import Enum_Permissions_Server
from app.classes.models.users import helper_users
from peewee import DoesNotExist
@ -18,10 +20,16 @@ from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
#Importing Models
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
# Importing Models
from app.classes.models.servers import servers_helper
#Importing Controllers
from app.classes.shared.console import console
from app.classes.shared.helpers import helper
from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
# Importing Controllers
from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
from app.classes.controllers.management_controller import Management_Controller
from app.classes.controllers.users_controller import Users_Controller
@ -29,11 +37,6 @@ from app.classes.controllers.roles_controller import Roles_Controller
from app.classes.controllers.server_perms_controller import Server_Perms_Controller
from app.classes.controllers.servers_controller import Servers_Controller
from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
logger = logging.getLogger(__name__)
class Controller:
@ -134,7 +137,8 @@ class Controller:
def package_support_logs(self, exec_user):
time.sleep(5)
#pausing so on screen notifications can run for user
time.sleep(7)
websocket_helper.broadcast_user(exec_user['user_id'], 'notification', 'Preparing your support logs')
tempDir = tempfile.mkdtemp()
tempZipStorage = tempfile.mkdtemp()
@ -159,7 +163,10 @@ class Controller:
for server in auth_servers:
final_path = os.path.join(server_path, str(server['server_name']))
os.mkdir(final_path)
shutil.copy(server['log_path'], final_path)
try:
shutil.copy(server['log_path'], final_path)
except Exception as e:
logger.warning("Failed to copy file with error: {}".format(e))
#Copy crafty logs to archive dir
full_log_name = os.path.join(crafty_path, 'logs')
shutil.copytree(os.path.join(self.project_root, 'logs'), full_log_name)
@ -168,12 +175,12 @@ class Controller:
tempZipStorage += '.zip'
websocket_helper.broadcast_user(exec_user['user_id'], 'send_logs_bootbox', {
})
self.users.set_support_path(exec_user['user_id'], tempZipStorage)
@staticmethod
def add_system_user():
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", helper_users.new_api_token(), False, False)
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", False, False)
def get_server_settings(self, server_id):
for s in self.servers_list:
@ -183,17 +190,17 @@ class Controller:
logger.warning("Unable to find server object for server id {}".format(server_id))
return False
def get_server_obj(self, server_id):
def get_server_obj(self, server_id: Union[str, int]) -> Union[bool, Server]:
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
if str(s['server_id']) == str(server_id):
return s['server_obj']
logger.warning("Unable to find server object for server id {}".format(server_id))
return False
return False # TODO: Change to None
def get_server_data(self, server_id):
def get_server_data(self, server_id: str):
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
if str(s['server_id']) == str(server_id):
return s['server_data_obj']
logger.warning("Unable to find server object for server id {}".format(server_id))
@ -251,6 +258,11 @@ class Controller:
server_id = helper.create_uuid()
server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
server_dir = helper.wtol_path(server_dir)
backup_path = helper.wtol_path(backup_path)
server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
server_file = "{server}-{version}.jar".format(server=server, version=version)
full_jar_path = os.path.join(server_dir, server_file)
@ -274,7 +286,12 @@ class Controller:
logger.error("Unable to create required server files due to :{}".format(e))
return False
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
if helper.is_os_windows():
server_command = 'java -Xms{}M -Xmx{}M -jar "{}" nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
else:
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
server_log_file = "{}/logs/latest.log".format(server_dir)
@ -307,6 +324,11 @@ class Controller:
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
@ -324,7 +346,13 @@ class Controller:
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
if helper.is_os_windows():
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
'"'+full_jar_path+'"')
else:
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
server_log_file = "{}/logs/latest.log".format(new_server_dir)
@ -338,6 +366,12 @@ class Controller:
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(' ', '^ ')
backup_path.replace(' ', '^ ')
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
@ -357,7 +391,13 @@ class Controller:
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
if helper.is_os_windows():
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
'"'+full_jar_path+'"')
else:
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
logger.debug('command: ' + server_command)
@ -373,6 +413,9 @@ class Controller:
old_bu_path = server_data['backup_path']
Server_Perms_Controller.backup_role_swap(old_server_id, new_server_id)
backup_path = helper.validate_traversal(helper.backup_path, old_bu_path)
if helper.is_os_windows():
backup_path = helper.wtol_path(backup_path)
backup_path.replace(' ', '^ ')
backup_path_components = list(backup_path.parts)
backup_path_components[-1] = new_uuid
new_bu_path = pathlib.PurePath(os.path.join(*backup_path_components))
@ -383,6 +426,7 @@ class Controller:
def register_server(self, name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port: int):
# put data in the db
new_id = self.servers.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port)
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
try:
@ -406,7 +450,7 @@ class Controller:
for s in self.servers_list:
# if this is the droid... im mean server we are looking for...
if int(s['server_id']) == int(server_id):
if str(s['server_id']) == str(server_id):
server_data = self.get_server_data(server_id)
server_name = server_data['server_name']
backup_dir = self.servers.get_server_data_by_id(server_id)['backup_path']
@ -427,7 +471,7 @@ class Controller:
if helper.check_path_exists(self.servers.get_server_data_by_id(server_id)['backup_path']):
shutil.rmtree(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['backup_path']))
#Cleanup scheduled tasks
try:
helpers_management.delete_scheduled_task_by_server(server_id)

View File

@ -10,6 +10,10 @@ from app.classes.minecraft.server_props import ServerProps
from app.classes.web.websocket_helper import websocket_helper
# To disable warning about unused import ; Users is imported from here in other places
Users = Users
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
@ -39,20 +43,16 @@ class db_builder:
username = default_data.get("username", 'admin')
password = default_data.get("password", 'crafty')
#api_token = helper.random_string_generator(32)
#
#Users.insert({
# Users.username: username.lower(),
# Users.password: helper.encode_pass(password),
# Users.api_token: api_token,
# Users.enabled: True,
# Users.superuser: True
#}).execute()
user_id = users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
#console.info("API token is {}".format(api_token))
@staticmethod
def is_fresh_install():
try:

View File

@ -4,13 +4,9 @@ import typing as t
import sys
import os
import re
from importlib import import_module
from functools import wraps
try:
from functools import cached_property
except ImportError:
from cached_property import cached_property
from functools import cached_property
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
@ -21,7 +17,7 @@ try:
import peewee
from playhouse.migrate import (
SchemaMigrator as ScM,
SqliteMigrator as SqM,
SqliteMigrator,
Operation, SQL, operation, SqliteDatabase,
make_index_name, Context
)
@ -32,6 +28,22 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1)
MIGRATE_TABLE = 'migratehistory'
MIGRATE_TEMPLATE = '''# Generated by database migrator
import peewee
def migrate(migrator, db):
"""
Write your migrations here.
"""
{migrate}
def rollback(migrator, db):
"""
Write your rollback migrations here.
"""
{rollback}'''
class MigrateHistory(peewee.Model):
"""
@ -41,30 +53,15 @@ class MigrateHistory(peewee.Model):
name = peewee.CharField(unique=True)
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
# noinspection PyTypeChecker
def __unicode__(self) -> str:
"""
String representation of this migration
"""
return self.name
MIGRATE_TABLE = 'migratehistory'
MIGRATE_TEMPLATE = '''# Generated by database migrator
def migrate(migrator, database, **kwargs):
"""
Write your migrations here.
"""
{migrate}
def rollback(migrator, database, **kwargs):
"""
Write your rollback migrations here.
"""
{rollback}'''
VOID: t.Callable = lambda m, d: None
class Meta:
table_name = MIGRATE_TABLE
def get_model(method):
@ -75,11 +72,12 @@ def get_model(method):
@wraps(method)
def wrapper(migrator, model, *args, **kwargs):
if isinstance(model, str):
return method(migrator, migrator.orm[model], *args, **kwargs)
return method(migrator, migrator.table_dict[model], *args, **kwargs)
return method(migrator, model, *args, **kwargs)
return wrapper
# noinspection PyProtectedMember
class Migrator(object):
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
"""
@ -88,8 +86,8 @@ class Migrator(object):
if isinstance(database, peewee.Proxy):
database = database.obj
self.database: SqliteDatabase = database
self.orm: t.Dict[str, peewee.Model] = {}
self.operations: t.List[Operation] = []
self.table_dict: t.Dict[str, peewee.Model] = {}
self.operations: t.List[t.Union[Operation, callable]] = []
self.migrator = SqliteMigrator(database)
def run(self):
@ -113,13 +111,13 @@ class Migrator(object):
"""
Executes raw SQL.
"""
self.operations.append(self.migrator.sql(sql, *params))
self.operations.append(SQL(sql, *params))
def create_table(self, model: peewee.Model) -> peewee.Model:
"""
Creates model and table in database.
"""
self.orm[model._meta.table_name] = model
self.table_dict[model._meta.table_name] = model
model._meta.database = self.database
self.operations.append(model.create_table)
return model
@ -129,8 +127,8 @@ class Migrator(object):
"""
Drops model and table from database.
"""
del self.orm[model._meta.table_name]
self.operations.append(self.migrator.drop_table(model))
del self.table_dict[model._meta.table_name]
self.operations.append(lambda: model.drop_table(cascade=False))
@get_model
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
@ -147,64 +145,16 @@ class Migrator(object):
return model
@get_model
def change_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
"""
Changes fields.
"""
for name, field in fields.items():
old_field = model._meta.fields.get(name, field)
old_column_name = old_field and old_field.column_name
model._meta.add_field(name, field)
if isinstance(old_field, peewee.ForeignKeyField):
self.operations.append(self.migrator.drop_foreign_key_constraint(
model._meta.table_name, old_column_name))
if old_column_name != field.column_name:
self.operations.append(
self.migrator.rename_column(
model._meta.table_name, old_column_name, field.column_name))
if isinstance(field, peewee.ForeignKeyField):
on_delete = field.on_delete if field.on_delete else 'RESTRICT'
on_update = field.on_update if field.on_update else 'RESTRICT'
self.operations.append(self.migrator.add_foreign_key_constraint(
model._meta.table_name, field.column_name,
field.rel_model._meta.table_name, field.rel_field.name,
on_delete, on_update))
continue
self.operations.append(self.migrator.change_column(
model._meta.table_name, field.column_name, field))
if field.unique == old_field.unique:
continue
if field.unique:
index = (field.column_name,), field.unique
self.operations.append(self.migrator.add_index(
model._meta.table_name, *index))
model._meta.indexes.append(index)
else:
index = (field.column_name,), old_field.unique
self.operations.append(self.migrator.drop_index(
model._meta.table_name, *index))
model._meta.indexes.remove(index)
return model
@get_model
def drop_columns(self, model: peewee.Model, names: str, **kwargs) -> peewee.Model:
def drop_columns(self, model: peewee.Model, names: str) -> peewee.Model:
"""
Removes fields from model.
"""
fields = [field for field in model._meta.fields.values()
if field.name in names]
cascade = kwargs.pop('cascade', True)
for field in fields:
self.__del_field__(model, field)
if field.unique:
# Drop unique index
index_name = make_index_name(
model._meta.table_name, [field.column_name])
self.operations.append(self.migrator.drop_index(
@ -250,16 +200,15 @@ class Migrator(object):
Renames table in database.
"""
old_name = model._meta.table_name
del self.orm[model._meta.table_name]
del self.table_dict[model._meta.table_name]
model._meta.table_name = new_name
self.orm[model._meta.table_name] = model
self.table_dict[model._meta.table_name] = model
self.operations.append(self.migrator.rename_table(old_name, new_name))
return model
@get_model
def add_index(self, model: peewee.Model, *columns: str, **kwargs) -> peewee.Model:
def add_index(self, model: peewee.Model, *columns: str, unique=False) -> peewee.Model:
"""Create indexes."""
unique = kwargs.pop('unique', False)
model._meta.indexes.append((columns, unique))
columns_ = []
for col in columns:
@ -329,42 +278,8 @@ class Migrator(object):
return model
class SqliteMigrator(SqM):
def drop_table(self, model):
return lambda: model.drop_table(cascade=False)
@operation
def change_column(self, table: str, column_name: str, field: peewee.Field):
operations = [self.alter_change_column(table, column_name, field)]
if not field.null:
operations.extend([self.add_not_null(table, column_name)])
return operations
def alter_change_column(self, table: str, column_name: str, field: peewee.Field) -> Operation:
return self._update_column(table, column_name, lambda x, y: y)
@operation
def sql(self, sql: str, *params) -> SQL:
"""
Executes raw SQL.
"""
return SQL(sql, *params)
def alter_add_column(
self, table: str, column_name: str, field: peewee.Field, **kwargs) -> Operation:
"""
Fixes field name for ForeignKeys.
"""
name = field.name
op = super().alter_add_column(
table, column_name, field, **kwargs)
if isinstance(field, peewee.ForeignKeyField):
field.name = name
return op
# noinspection PyProtectedMember
class MigrationManager(object):
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
@ -376,7 +291,7 @@ class MigrationManager(object):
self.database = database
@cached_property
def model(self) -> peewee.Model:
def model(self) -> t.Type[MigrateHistory]:
"""
Initialize and cache the MigrationHistory model.
"""
@ -487,7 +402,7 @@ class MigrationManager(object):
scope = {}
code = compile(code, '<string>', 'exec', dont_inherit=True)
exec(code, scope, None)
return scope.get('migrate', VOID), scope.get('rollback', VOID)
return scope.get('migrate', lambda m, d: None), scope.get('rollback', lambda m, d: None)
def up_one(self, name: str, migrator: Migrator,
fake: bool = False, rollback: bool = False) -> str:
@ -518,11 +433,11 @@ class MigrationManager(object):
except Exception:
self.database.rollback()
operation = 'Rollback' if rollback else 'Migration'
logger.exception('{} failed: {}'.format(operation, name))
operation_name = 'Rollback' if rollback else 'Migration'
logger.exception('{} failed: {}'.format(operation_name, name))
raise
def down(self, name: t.Optional[str] = None):
def down(self):
"""
Rolls back migrations.
"""

View File

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

View File

@ -25,6 +25,7 @@ from app.classes.models.management import management_helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.translation import translation
from app.classes.models.users import users_helper
from app.classes.models.server_permissions import server_permissions
logger = logging.getLogger(__name__)
@ -119,6 +120,8 @@ class Server:
self.restart_count = 0
self.crash_watcher_schedule = None
self.stats = stats
tz = get_localzone()
self.server_scheduler = BackgroundScheduler(timezone=str(tz))
self.backup_thread = threading.Thread(target=self.a_backup_server, daemon=True, name=f"backup_{self.name}")
self.is_backingup = False
@ -144,8 +147,6 @@ class Server:
logger.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
console.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
tz = get_localzone()
self.server_scheduler = BackgroundScheduler(timezone=str(tz))
self.server_scheduler.add_job(self.run_scheduled_server, 'interval', seconds=delay, id=str(self.server_id))
self.server_scheduler.start()
@ -289,8 +290,15 @@ class Server:
websocket_helper.broadcast_user(user_id, 'send_start_error', {
'error': translation.translate('error', 'portReminder', user_lang).format(self.name, loc_server_port)
})
server_users = server_permissions.get_server_user_list(self.server_id)
for user in server_users:
if user != user_id:
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
else:
websocket_helper.broadcast_user(user_id, 'send_start_reload', {
server_users = server_permissions.get_server_user_list(self.server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
else:
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.process.pid))
@ -300,7 +308,7 @@ class Server:
logger.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
console.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
self.crash_watcher_schedule = schedule.every(30).seconds.do(self.detect_crash).tag(self.name)
self.crash_watcher_schedule = self.server_scheduler.add_job(self.detect_crash, 'interval', seconds=30, id="crash_watcher")
def check_internet_thread(self, user_id, user_lang):
if user_id:
@ -352,9 +360,14 @@ class Server:
# massive resetting of variables
self.cleanup_server_object()
server_users = server_permissions.get_server_user_list(self.server_id)
self.stats.record_stats()
for user in server_users:
websocket_helper.broadcast_user(user, 'send_start_reload', {
})
def restart_threaded_server(self, user_id):
# if not already running, let's just start
if not self.check_running():
@ -383,11 +396,10 @@ class Server:
return False
def send_command(self, command):
console.info("COMMAND TIME: {}".format(command))
if not self.check_running() and command.lower() != 'start':
logger.warning("Server not running, unable to send command \"{}\"".format(command))
return False
console.info("COMMAND TIME: {}".format(command))
logger.debug("Sending command {} to server".format(command))
# send it
@ -477,7 +489,7 @@ class Server:
def remove_watcher_thread(self):
logger.info("Removing old crash detection watcher thread")
console.info("Removing old crash detection watcher thread")
schedule.clear(self.name)
self.crash_watcher_schedule.remove(self.server_name)
def agree_eula(self, user_id):
file = os.path.join(self.server_path, 'eula.txt')
@ -610,7 +622,9 @@ class Server:
if len(websocket_helper.clients) > 0:
# There are clients
self.check_update()
websocket_helper.broadcast('notification', "Executable update finished for " + self.name)
server_users = server_permissions.get_server_user_list(self.server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable update finished for " + self.name)
time.sleep(3)
websocket_helper.broadcast_page('/panel/server_detail', 'update_button_status', {
'isUpdating': self.check_update(),
@ -619,15 +633,18 @@ class Server:
})
websocket_helper.broadcast_page('/panel/dashboard', 'send_start_reload', {
})
websocket_helper.broadcast('notification', "Executable update finished for "+self.name)
server_users = server_permissions.get_server_user_list(self.server_id)
for user in server_users:
websocket_helper.broadcast_user(user, 'notification', "Executable update finished for "+self.name)
management_helper.add_to_audit_log_raw('Alert', '-1', self.server_id, "Executable update finished for "+self.name, self.settings['server_ip'])
if wasStarted:
self.start_server()
elif not downloaded and not self.is_backingup:
time.sleep(5)
servers_helper.set_update(self.server_id, False)
websocket_helper.broadcast('notification',
server_users = server_permissions.get_server_user_list(self.server_id)
for user in server_users:
websocket_helper.broadcast_user(user,'notification',
"Executable update failed for " + self.name + ". Check log file for details.")
logger.error("Executable download failed.")
pass

View File

@ -1,4 +1,5 @@
from datetime import timedelta
from http import server
import os
import sys
import json
@ -25,6 +26,7 @@ logger = logging.getLogger('apscheduler')
try:
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True)
@ -51,8 +53,8 @@ class TasksManager:
self.controller = controller
self.tornado = Webserver(controller, self)
tz = get_localzone()
self.scheduler = BackgroundScheduler(timezone=str(tz))
self.tz = get_localzone()
self.scheduler = BackgroundScheduler(timezone=str(self.tz))
self.users_controller = Users_Controller()
@ -79,16 +81,20 @@ class TasksManager:
logger.info("Reload from DB called. Current enabled schedules: ")
for item in jobs:
logger.info("JOB: {}".format(item))
def command_watcher(self):
while True:
# select any commands waiting to be processed
commands = management_helper.get_unactioned_commands()
for c in commands:
svr = self.controller.get_server_obj(c['server_id']['server_id'])
user_id = c.get('user')['user_id']
command = c.get('command', None)
try:
svr = self.controller.get_server_obj(c.server_id)
except:
logger.error("Server value requested does note exist purging item from waiting commands.")
management_helper.mark_command_complete(c.command_id)
user_id = c.user_id
command = c.command
if command == 'start_server':
svr.run_threaded_server(user_id)
@ -106,7 +112,7 @@ class TasksManager:
svr.jar_update()
else:
svr.send_command(command)
management_helper.mark_command_complete(c.get('command_id', None))
management_helper.mark_command_complete(c.command_id)
time.sleep(1)
@ -153,11 +159,19 @@ class TasksManager:
schedules = management_helper.get_schedules_enabled()
self.scheduler.add_listener(self.schedule_watcher, mask=EVENT_JOB_EXECUTED)
#self.scheduler.add_job(self.scheduler.print_jobs, 'interval', seconds=10, id='-1')
#load schedules from DB
for schedule in schedules:
if schedule.cron_string != "":
cron = schedule.cron_string.split(' ')
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
try:
self.scheduler.add_job(management_helper.add_command, CronTrigger.from_crontab(schedule.cron_string, timezone=str(self.tz)), id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
except Exception as e:
console.error("Failed to schedule task with error: {}.".format(e))
console.warning("Removing failed task from DB.")
logger.error("Failed to schedule task with error: {}.".format(e))
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(schedule.schedule_id)
else:
if schedule.interval_type == 'hours':
self.scheduler.add_job(management_helper.add_command, 'cron', minute = 0, hour = '*/'+str(schedule.interval), id=str(schedule.schedule_id), args=[schedule.server_id, self.users_controller.get_id_by_name('system'), '127.0.0.1', schedule.command])
@ -177,12 +191,14 @@ class TasksManager:
sch_id = management_helper.create_scheduled_task(job_data['server_id'], job_data['action'], job_data['interval'], job_data['interval_type'], job_data['start_time'], job_data['command'], "None", job_data['enabled'], job_data['one_time'], job_data['cron_string'])
if job_data['enabled']:
if job_data['cron_string'] != "":
cron = job_data['cron_string'].split(' ')
try:
self.scheduler.add_job(management_helper.add_command, 'cron', minute = cron[0], hour = cron[1], day = cron[2], month = cron[3], day_of_week = cron[4], id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
self.scheduler.add_job(management_helper.add_command, CronTrigger.from_crontab(job_data['cron_string'], timezone=str(self.tz)), id=str(sch_id), args=[job_data['server_id'], self.users_controller.get_id_by_name('system'), '127.0.0.1', job_data['command']])
except Exception as e:
console.error("Failed to schedule task with error: {}.".format(e))
console.info("Removing failed task from DB.")
console.warning("Removing failed task from DB.")
logger.error("Failed to schedule task with error: {}.".format(e))
logger.warning("Removing failed task from DB.")
#remove items from DB if task fails to add to apscheduler
management_helper.delete_scheduled_task(sch_id)
else:
if job_data['interval_type'] == 'hours':
@ -197,13 +213,19 @@ class TasksManager:
for item in jobs:
logger.info("JOB: {}".format(item))
def remove_all_server_tasks(self, server_id):
schedules = management_helper.get_schedules_by_server(server_id)
for schedule in schedules:
self.remove_job(schedule.schedule_id)
def remove_job(self, sch_id):
job = management_helper.get_scheduled_task_model(sch_id)
management_helper.delete_scheduled_task(sch_id)
if job.enabled:
self.scheduler.remove_job(str(sch_id))
logger.info("Job with ID {} was deleted.".format(sch_id))
else:
logger.info("Job with ID {} was deleted from DB, but was not enabled. Not going to try removing something that doesn't exist from active schedules.")
logger.info("Job with ID {} was deleted from DB, but was not enabled. Not going to try removing something that doesn't exist from active schedules.".format(sch_id))
def update_job(self, sch_id, job_data):
management_helper.update_scheduled_task(sch_id, job_data)
@ -264,8 +286,7 @@ class TasksManager:
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
self.scheduler.add_job(server_jar_obj.refresh_cache, 'interval', hours=12, id="serverjars")
@staticmethod
def realtime():
def realtime(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@ -290,7 +311,69 @@ class TasksManager:
'mem_percent': host_stats.get('mem_percent'),
'mem_usage': host_stats.get('mem_usage')
})
time.sleep(4)
servers = self.controller.servers_list
servers_ping = []
for srv in servers:
server_data = srv.get('server_data_obj', False)
if server_data:
server_id = server_data.get('server_id', False)
srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id)
if ("{}".format(srv['raw_ping_result'].get('icon')) == "b''"):
srv['raw_ping_result']['icon'] = False
servers_ping.append({
'id': srv['raw_ping_result'].get('id'),
'started': srv['raw_ping_result'].get('started'),
'running': srv['raw_ping_result'].get('running'),
'cpu': srv['raw_ping_result'].get('cpu'),
'mem': srv['raw_ping_result'].get('mem'),
'mem_percent': srv['raw_ping_result'].get('mem_percent'),
'world_name': srv['raw_ping_result'].get('world_name'),
'world_size': srv['raw_ping_result'].get('world_size'),
'server_port': srv['raw_ping_result'].get('server_port'),
'int_ping_results': srv['raw_ping_result'].get('int_ping_results'),
'online': srv['raw_ping_result'].get('online'),
'max': srv['raw_ping_result'].get('max'),
'players': srv['raw_ping_result'].get('players'),
'desc': srv['raw_ping_result'].get('desc'),
'version': srv['raw_ping_result'].get('version'),
'icon': srv['raw_ping_result'].get('icon')
})
if (len(websocket_helper.clients) > 0):
websocket_helper.broadcast_page_params(
'/panel/server_detail',
{
'id': str(server_id)
},
'update_server_details',
{
'id': srv['raw_ping_result'].get('id'),
'started': srv['raw_ping_result'].get('started'),
'running': srv['raw_ping_result'].get('running'),
'cpu': srv['raw_ping_result'].get('cpu'),
'mem': srv['raw_ping_result'].get('mem'),
'mem_percent': srv['raw_ping_result'].get('mem_percent'),
'world_name': srv['raw_ping_result'].get('world_name'),
'world_size': srv['raw_ping_result'].get('world_size'),
'server_port': srv['raw_ping_result'].get('server_port'),
'int_ping_results': srv['raw_ping_result'].get('int_ping_results'),
'online': srv['raw_ping_result'].get('online'),
'max': srv['raw_ping_result'].get('max'),
'players': srv['raw_ping_result'].get('players'),
'desc': srv['raw_ping_result'].get('desc'),
'version': srv['raw_ping_result'].get('version'),
'icon': srv['raw_ping_result'].get('icon')
}
)
if (len(servers_ping) > 0) & (len(websocket_helper.clients) > 0):
try:
websocket_helper.broadcast_page('/panel/dashboard', 'update_server_status', servers_ping)
websocket_helper.broadcast_page('/status', 'update_server_status', servers_ping)
except:
console.warning("Can't broadcast server status to websocket")
time.sleep(5)
def log_watcher(self):
self.controller.servers.check_for_old_logs()

View File

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

View File

@ -35,13 +35,13 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated
def get(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
_, _, exec_user = self.current_user
error = bleach.clean(self.get_argument('error', "WTF Error!"))
template = "panel/denied.html"
page_data = {
'user_data': user_data,
'user_data': exec_user,
'error': error
}
@ -164,10 +164,13 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated
def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -178,17 +181,17 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
error = bleach.clean(self.get_argument('error', "WTF Error!"))
page_data = {
'user_data': user_data,
'user_data': exec_user,
'error': error
}
if page == "send_command":
command = self.get_body_argument('command', default=None, strip=True)
server_id = self.get_argument('id')
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in send_command ajax call")
@ -196,15 +199,23 @@ class AjaxHandler(BaseHandler):
srv_obj = self.controller.get_server_obj(server_id)
if command == srv_obj.settings['stop_command']:
logger.info("Stop command detected as terminal input - intercepting. Starting Crafty's stop process for server with id: {}.".format(server_id))
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'stop_server')
command = None
elif command == 'restart':
logger.info("Restart command detected as terminal input - intercepting. Starting Crafty's stop process for server with id: {}.".format(server_id))
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), 'restart_server')
command = None
if command:
if srv_obj.check_running():
srv_obj.send_command(command)
self.controller.management.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
self.controller.management.add_to_audit_log(exec_user['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
elif page == "create_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_parent = helper.get_os_understandable_path(self.get_body_argument('file_parent', default=None, strip=True))
@ -227,7 +238,7 @@ class AjaxHandler(BaseHandler):
elif page == "create_dir":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
dir_parent = helper.get_os_understandable_path(self.get_body_argument('dir_parent', default=None, strip=True))
@ -248,7 +259,7 @@ class AjaxHandler(BaseHandler):
elif page == "unzip_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
server_id = self.get_argument('id', None)
@ -259,7 +270,7 @@ class AjaxHandler(BaseHandler):
elif page == "kill":
if not permissions['Commands'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Commands")
return
server_id = self.get_argument('id', None)
@ -272,11 +283,11 @@ class AjaxHandler(BaseHandler):
elif page == "eula":
server_id = self.get_argument('id', None)
svr = self.controller.get_server_obj(server_id)
svr.agree_eula(user_data['user_id'])
svr.agree_eula(exec_user['user_id'])
elif page == "restore_backup":
if not permissions['Backup'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups")
return
server_id = bleach.clean(self.get_argument('id', None))
@ -295,16 +306,21 @@ class AjaxHandler(BaseHandler):
elif page == "unzip_server":
path = self.get_argument('path', None)
helper.unzipServer(path, exec_user_id)
helper.unzipServer(path, exec_user['user_id'])
return
@tornado.web.authenticated
def delete(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -315,10 +331,10 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
if page == "del_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
@ -350,7 +366,7 @@ class AjaxHandler(BaseHandler):
if page == "del_backup":
if not permissions['Backup'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups")
return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
@ -376,7 +392,7 @@ class AjaxHandler(BaseHandler):
elif page == "del_dir":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
dir_path = helper.get_os_understandable_path(self.get_body_argument('dir_path', default=None, strip=True))
@ -401,30 +417,52 @@ class AjaxHandler(BaseHandler):
elif page == "delete_server":
if not permissions['Config'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument('id', None)
logger.info(
"Removing server from panel for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
server_data = self.controller.get_server_data(server_id)
server_name = server_data['server_name']
self.controller.management.add_to_audit_log(exec_user['user_id'],
"Deleted server {} named {}".format(server_id, server_name),
server_id,
self.get_remote_ip())
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, False)
elif page == "delete_server_files":
if not permissions['Config'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument('id', None)
logger.info(
"Removing server and all associated files for server: {}".format(self.controller.servers.get_server_friendly_name(server_id)))
server_data = self.controller.get_server_data(server_id)
server_name = server_data['server_name']
self.controller.management.add_to_audit_log(exec_user['user_id'],
"Deleted server {} named {}".format(server_id, server_name),
server_id,
self.get_remote_ip())
self.tasks_manager.remove_all_server_tasks(server_id)
self.controller.remove_server(server_id, True)
@tornado.web.authenticated
def put(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -435,10 +473,10 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
if page == "save_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
@ -460,7 +498,7 @@ class AjaxHandler(BaseHandler):
elif page == "rename_item":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True))

View File

@ -1,14 +1,11 @@
import os
import secrets
import threading
import tornado.web
import tornado.escape
import logging
import re
from app.classes.web.base_handler import BaseHandler
log = logging.getLogger(__name__)
bearer_pattern = re.compile(r'^Bearer', flags=re.IGNORECASE)
class ApiHandler(BaseHandler):
@ -16,7 +13,7 @@ class ApiHandler(BaseHandler):
# Define a standardized response
self.set_status(status)
self.write(data)
def access_denied(self, user, reason=''):
if reason: reason = ' because ' + reason
log.info("User %s from IP %s was denied access to the API route " + self.request.path + reason, user, self.get_remote_ip())
@ -28,8 +25,14 @@ class ApiHandler(BaseHandler):
def authenticate_user(self) -> bool:
try:
log.debug("Searching for specified token")
# TODO: YEET THIS
user_data = self.controller.users.get_user_by_api_token(self.get_argument('token'))
api_token = self.get_argument('token', '')
if api_token is None and self.request.headers.get('Authorization'):
api_token = bearer_pattern.sub('', self.request.headers.get('Authorization'))
elif api_token is None:
api_token = self.get_cookie('token')
user_data = self.controller.users.get_user_by_api_token(api_token)
log.debug("Checking results")
if user_data:
# Login successful! Check perms
@ -40,11 +43,11 @@ class ApiHandler(BaseHandler):
else:
logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token")
return
return False
except Exception as e:
log.warning("An error occured while authenticating an API user: %s", e)
self.access_denied("unknown"), "an error occured while authenticating the user"
return
return False
class ServersStats(ApiHandler):

View File

@ -4,10 +4,12 @@ import bleach
from typing import (
Union,
List,
Optional
Optional, Tuple, Dict, Any
)
from app.classes.shared.authentication import authentication
from app.classes.shared.main_controller import Controller
from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__)
@ -17,7 +19,8 @@ class BaseHandler(tornado.web.RequestHandler):
nobleach = {bool, type(None)}
redactables = ("pass", "api")
def initialize(self, controller : Controller = None, tasks_manager=None, translator=None):
# noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller = None, tasks_manager=None, translator=None):
self.controller = controller
self.tasks_manager = tasks_manager
self.translator = translator
@ -28,8 +31,9 @@ class BaseHandler(tornado.web.RequestHandler):
self.request.remote_ip
return remote_ip
def get_current_user(self):
return self.get_secure_cookie("user", max_age_days=1)
current_user: Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]
def get_current_user(self) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
return authentication.check(self.get_cookie("token"))
def autobleach(self, name, text):
for r in self.redactables:

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import requests
import tornado.web
import tornado.escape
from app.classes.shared.authentication import authentication
from app.classes.shared.helpers import Helpers, helper
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.console import console
@ -27,7 +28,7 @@ except ModuleNotFoundError as e:
class PublicHandler(BaseHandler):
def set_current_user(self, user):
def set_current_user(self, user_id: str = None):
expire_days = helper.get_setting('cookie_expire')
@ -35,8 +36,8 @@ class PublicHandler(BaseHandler):
if not expire_days:
expire_days = "5"
if user:
self.set_secure_cookie("user", tornado.escape.json_encode(user), expires_days=int(expire_days))
if user_id is not None:
self.set_cookie("token", authentication.generate(user_id), expires_days=int(expire_days))
else:
self.clear_cookie("user")
@ -45,12 +46,7 @@ class PublicHandler(BaseHandler):
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
error_msg = bleach.clean(self.get_argument('error_msg', ''))
page_data = {
'version': helper.get_version_string(),
'error': error
}
page_data['lang'] = tornado.locale.get("en_EN")
page_data = {'version': helper.get_version_string(), 'error': error, 'lang': helper.get_setting('language')}
# sensible defaults
template = "public/404.html"
@ -112,7 +108,7 @@ class PublicHandler(BaseHandler):
# Valid Login
if login_result:
self.set_current_user(entered_username)
self.set_current_user(user_data.user_id)
logger.info("User: {} Logged in from IP: {}".format(user_data, self.get_remote_ip()))
# record this login
@ -124,32 +120,6 @@ class PublicHandler(BaseHandler):
# log this login
self.controller.management.add_to_audit_log(user_data.user_id, "Logged in", 0, self.get_remote_ip())
if helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x"
else:
rating = "g"
#Get grvatar hash for profile pictures
if user_data.email != 'default@example.com' or "":
g = libgravatar.Gravatar(libgravatar.sanitize_email(user_data.email))
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
if requests.head(url).status_code != 404:
profile_url = url
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
cookie_data = {
"username": user_data.username,
"user_id": user_data.user_id,
"email": user_data.email,
"profile_url": profile_url,
"account_type": user_data.superuser,
}
self.set_secure_cookie('user_data', json.dumps(cookie_data))
next_page = "/panel/dashboard"
self.redirect(next_page)
else:

View File

@ -9,6 +9,8 @@ from app.classes.web.base_handler import BaseHandler
from app.classes.models.crafty_permissions import Enum_Permissions_Crafty
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.shared.helpers import helper
import libgravatar
import requests
logger = logging.getLogger(__name__)
@ -28,13 +30,13 @@ class ServerHandler(BaseHandler):
@tornado.web.authenticated
def get(self, page):
# name = tornado.escape.json_decode(self.current_user)
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user_id = exec_user_data['user_id']
exec_user = self.controller.users.get_user_by_id(exec_user_id)
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
exec_user_role = set()
if exec_user['superuser'] == 1:
if superuser:
defined_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User")
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
@ -42,8 +44,8 @@ class ServerHandler(BaseHandler):
for role in self.controller.roles.get_all_roles():
list_roles.append(self.controller.roles.get_role(role.role_id))
else:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user["user_id"])
defined_servers = self.controller.servers.get_authorized_servers(exec_user["user_id"])
list_roles = []
for r in exec_user['roles']:
role = self.controller.roles.get_role(r)
@ -54,7 +56,7 @@ class ServerHandler(BaseHandler):
page_data = {
'version_data': helper.get_version_string(),
'user_data': exec_user_data,
'user_data': exec_user,
'user_role' : exec_user_role,
'roles' : list_roles,
'user_crafty_permissions' : exec_user_crafty_permissions,
@ -71,13 +73,38 @@ class ServerHandler(BaseHandler):
'hosts_data': self.controller.management.get_latest_hosts_stats(),
'menu_servers': defined_servers,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
'api_key': {
'name': api_key.name,
'created': api_key.created,
'server_permissions': api_key.server_permissions,
'crafty_permissions': api_key.crafty_permissions,
'superuser': api_key.superuser
} if api_key is not None else None,
'superuser': superuser
}
if exec_user['superuser'] == 1:
if helper.get_setting("allow_nsfw_profile_pictures"):
rating = "x"
else:
rating = "g"
if exec_user['email'] != 'default@example.com' or "":
g = libgravatar.Gravatar(libgravatar.sanitize_email(exec_user['email']))
url = g.get_image(size=80, default="404", force_default=False, rating=rating, filetype_extension=False, use_ssl=True) # + "?d=404"
if requests.head(url).status_code != 404:
profile_url = url
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
page_data['user_image'] = profile_url
if superuser:
page_data['roles'] = list_roles
if page == "step1":
if not exec_user['superuser'] and not self.controller.crafty_perms.can_create_server(exec_user_id):
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
return
@ -93,17 +120,17 @@ class ServerHandler(BaseHandler):
@tornado.web.authenticated
def post(self, page):
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user_id = exec_user_data['user_id']
exec_user = self.controller.users.get_user_by_id(exec_user_id)
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
template = "public/404.html"
page_data = {
'version_data': "version_data_here",
'user_data': exec_user_data,
'version_data': "version_data_here", # TODO
'user_data': exec_user,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"])
}
if page == "command":
@ -151,11 +178,11 @@ class ServerHandler(BaseHandler):
return
self.controller.management.send_command(exec_user_data['user_id'], server_id, self.get_remote_ip(), command)
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), command)
if page == "step1":
if not exec_user['superuser']:
if not superuser:
user_roles = self.controller.roles.get_all_roles()
else:
user_roles = self.controller.roles.get_all_roles()
@ -185,7 +212,7 @@ class ServerHandler(BaseHandler):
return
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"imported a jar server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
@ -201,7 +228,7 @@ class ServerHandler(BaseHandler):
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
return
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
@ -213,21 +240,21 @@ class ServerHandler(BaseHandler):
return
server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar
role_ids = self.controller.users.get_user_roles_id(exec_user_id)
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not exec_user['superuser']:
if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.users.add_role_to_user(exec_user_id, role_id)
self.controller.crafty_perms.add_server_creation(exec_user_id)
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
else:
for role in captured_roles:

View File

@ -25,7 +25,7 @@ except ModuleNotFoundError as e:
class StatusHandler(BaseHandler):
def get(self):
page_data = {}
page_data['lang'] = tornado.locale.get("en_EN")
page_data['lang'] = helper.get_setting('language')
page_data['servers'] = self.controller.servers.get_all_servers_stats()
for srv in page_data['servers']:
server_data = srv.get('server_data', False)

View File

@ -116,6 +116,7 @@ class Webserver:
tornado.template.Loader('.')
# TODO: Remove because we don't and won't use
tornado.locale.set_default_locale('en_EN')
handler_args = {"controller": self.controller, "tasks_manager": self.tasks_manager, "translator": translation}

View File

@ -20,6 +20,7 @@ MAX_STREAMED_SIZE = 1024 * 1024 * 1024
@tornado.web.stream_request_body
class UploadHandler(tornado.web.RequestHandler):
# noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller=None, tasks_manager=None, translator=None):
self.controller = controller
self.tasks_manager = tasks_manager
@ -27,8 +28,19 @@ class UploadHandler(tornado.web.RequestHandler):
def prepare(self):
self.do_upload = True
user_data = json.loads(self.get_secure_cookie('user_data'))
user_id = user_data['user_id']
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
user_id = exec_user['user_id']
if superuser:
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
elif api_key is not None:
exec_user_crafty_permissions = self.controller.crafty_perms.get_api_key_permissions_list(api_key)
else:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(
exec_user["user_id"])
server_id = self.request.headers.get('X-ServerId', None)
@ -42,8 +54,7 @@ class UploadHandler(tornado.web.RequestHandler):
console.warning('Server ID not found in upload handler call')
self.do_upload = False
user_permissions = self.controller.server_perms.get_user_permissions_list(user_id, server_id)
if Enum_Permissions_Server.Files not in user_permissions:
if Enum_Permissions_Server.Files not in exec_user_crafty_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
self.do_upload = False

View File

@ -5,6 +5,7 @@ import sys
from urllib.parse import parse_qsl
from app.classes.models.users import Users
from app.classes.shared.authentication import authentication
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.console import console
@ -19,7 +20,14 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e, e.name))
sys.exit(1)
class SocketHandler(tornado.websocket.WebSocketHandler):
page = None
page_query_params = None
controller = None
tasks_manager = None
translator = None
io_loop = None
def initialize(self, controller=None, tasks_manager=None, translator=None):
self.controller = controller
@ -34,24 +42,11 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
return remote_ip
def get_user_id(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
user_data_cookie = user_data_cookie_raw.decode('utf-8')
user_id = json.loads(user_data_cookie)['user_id']
return user_id
_, _, user = authentication.check(self.get_cookie('token'))
return user['user_id']
def check_auth(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
user_data_cookie = user_data_cookie_raw.decode('utf-8')
user_id = json.loads(user_data_cookie)['user_id']
query = Users.select().where(Users.user_id == user_id)
if query.exists():
return True
return False
return authentication.check_bool(self.get_cookie('token'))
def open(self):
logger.debug('Checking WebSocket authentication')
@ -74,10 +69,11 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
logger.debug('Opened WebSocket connection')
# websocket_helper.broadcast('notification', 'New client connected')
def on_message(self, rawMessage):
@staticmethod
def on_message(raw_message):
logger.debug('Got message from WebSocket connection {}'.format(rawMessage))
message = json.loads(rawMessage)
logger.debug('Got message from WebSocket connection {}'.format(raw_message))
message = json.loads(raw_message)
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
def on_close(self):

View File

@ -5,6 +5,7 @@
"language": "en_EN",
"cookie_expire": 30,
"cookie_secret": "random",
"apikey_secret": "random",
"show_errors": true,
"history_max_age": 7,
"stats_update_frequency": 30,

View File

@ -50,7 +50,11 @@
}
.mc-log-error{
color:#ff6258;
color:#af463f;
}
.mc-log-fatal{
color:#da0f00;
}
.mc-log-keyword{

View File

@ -1,153 +1,162 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block meta %}{% end %}
<title>{% block title %}{{ _('Default') }}{% end %}</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css"/>
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block meta %}{% end %}
<title>{% block title %}{{ _('Default') }}{% end %}</title>
<!-- endinject -->
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
<link rel="stylesheet" type="text/css"
href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" />
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- Plugin css for this page -->
<link rel="stylesheet" href="/static/assets/vendors/jvectormap/jquery-jvectormap.css">
<!-- End Plugin css for this page -->
<!-- endinject -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<!-- Plugin css for this page -->
<link rel="stylesheet" href="/static/assets/vendors/jvectormap/jquery-jvectormap.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- End Plugin css for this page -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
</head>
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
<link rel="stylesheet" href="/static/assets/css/crafty.css">
<body class="dark-theme">
<div class="container-scroller">
<!-- partial:partials/_navbar.html -->
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
<div class="text-center navbar-brand-wrapper d-flex align-items-top justify-content-center">
<a class="navbar-brand brand-logo" href="/panel/dashboard">
<img src="/static/assets/images/logo_long.svg" alt="logo" /> </a>
<a class="navbar-brand brand-logo-mini" href="/panel/dashboard">
<img src="/static/assets/images/logo_small.svg" alt="logo" /> </a>
</div>
<div class="navbar-menu-wrapper d-flex align-items-center">
<button class="navbar-toggler navbar-toggler align-self-center" type="button" data-toggle="minimize">
<span class="mdi mdi-menu"></span>
</button>
</head>
<body class="dark-theme">
<div class="container-scroller">
<!-- partial:partials/_navbar.html -->
<nav class="navbar default-layout col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
<div class="text-center navbar-brand-wrapper d-flex align-items-top justify-content-center">
<a class="navbar-brand brand-logo" href="/panel/dashboard">
<img src="/static/assets/images/logo_long.svg" alt="logo" /> </a>
<a class="navbar-brand brand-logo-mini" href="/panel/dashboard">
<img src="/static/assets/images/logo_small.svg" alt="logo" /> </a>
</div>
<div class="navbar-menu-wrapper d-flex align-items-center">
<button class="navbar-toggler navbar-toggler align-self-center" type="button" data-toggle="minimize">
<span class="mdi mdi-menu"></span>
</button>
{% include notify.html %}
<button class="navbar-toggler navbar-toggler-right d-lg-none align-self-center" type="button" data-toggle="offcanvas">
<span class="mdi mdi-menu"></span>
</button>
</div>
</nav>
{% include main_menu.html %}
<div class="main-panel">
<div class="warnings">
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
</noscript>
</div>
{% block content %}
{% end %}
{% include footer.html %}
</div>
<!-- main-panel ends -->
<button class="navbar-toggler navbar-toggler-right d-lg-none align-self-center" type="button"
data-toggle="offcanvas">
<span class="mdi mdi-menu"></span>
</button>
</div>
<!-- page-body-wrapper ends -->
</nav>
{% include main_menu.html %}
<div class="main-panel">
<div class="warnings">
<noscript class="noscript-warning" style="padding: 20px; background-color: rgb(247, 151, 15);">
<div>{% raw translate('base', 'doesNotWorkWithoutJavascript', data['lang']) %}</div>
</noscript>
</div>
{% block content %}
{% end %}
{% include footer.html %}
</div>
<!-- main-panel ends -->
</div>
<!-- page-body-wrapper ends -->
</div>
<style>
.notifications {
position: fixed;
width: 200px;
top: 70px;
right: 0px;
}
.notification {
position: relative;
box-sizing: border-box;
padding: 0.5rem;
padding-left: 0.7rem;
width: 180px;
margin-left: 10px;
margin-right: 10px;
background: #282a40;
transition: right 0.75s, opacity 0.75s, top 0.75s;
right: -6rem;
opacity: 0.1;
margin-bottom: 1rem;
z-index: 999;
top: 0px;
}
.notification.active {
right: 0rem;
opacity: 1;
}
.notification.remove {
right: 0rem;
opacity: 0.1;
top: -2rem;
}
.notification p {
margin: 0px;
width: calc(160.8px - 16px);
z-index: inherit;
}
.notification span {
position: absolute;
right: 0.5rem;
top: 0.46rem;
cursor: pointer;
font-weight: bold;
line-height: 20px;
font-size: 22px;
user-select: none;
z-index: inherit;
}
</style>
<div class="notifications"></div>
<style>
.notifications {
position: fixed;
width: 200px;
top: 70px;
right: 0px;
}
.notification {
position: relative;
box-sizing: border-box;
padding: 0.5rem;
padding-left: 0.7rem;
width: 180px;
margin-left: 10px;
margin-right: 10px;
background: #282a40;
transition: right 0.75s, opacity 0.75s, top 0.75s;
right: -6rem;
opacity: 0.1;
margin-bottom: 1rem;
z-index: 999;
top: 0px;
}
.notification.active {
right: 0rem;
opacity: 1;
}
.notification.remove {
right: 0rem;
opacity: 0.1;
top: -2rem;
}
.notification p {
margin: 0px;
width: calc(160.8px - 16px);
z-index: inherit;
}
.notification span {
position: absolute;
right: 0.5rem;
top: 0.46rem;
cursor: pointer;
font-weight: bold;
line-height: 20px;
font-size: 22px;
user-select: none;
z-index: inherit;
}
</style>
<div class="notifications"></div>
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
<script type="text/javascript" src="/static/assets/js/motd.js"></script>
<script>
$.extend($.fn.dataTable.defaults, {
language: {% raw translate('datatables', 'i18n', data['lang']) %}
<script>
$.extend($.fn.dataTable.defaults, {
language: {% raw translate('datatables', 'i18n', data['lang']) %}
})
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
// tool tips
@ -156,13 +165,13 @@
})
// Notify
$(document).ready(function(){
$("#notificationDropdown").click(function(){
$.get("/ajax/announcements", function(data){
$(document).ready(function () {
$("#notificationDropdown").click(function () {
$.get("/ajax/announcements", function (data) {
console.log(data);
bootbox.alert({
title: "Notifications",
message: data,
title: "Notifications",
message: data,
});
});
@ -170,146 +179,150 @@
});
// {% if request.protocol == 'https' %}
let usingWebSockets = true;
let usingWebSockets = true;
let listenEvents = [];
let listenEvents = [];
try {
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
page = 'page=' + encodeURIComponent(location.pathname)
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
wsInternal.onopen = function() {
console.log('opened WebSocket connection:', wsInternal)
};
wsInternal.onmessage = function (rawMessage) {
var message = JSON.parse(rawMessage.data);
try {
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
page = 'page=' + encodeURIComponent(location.pathname)
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
wsInternal.onopen = function () {
console.log('opened WebSocket connection:', wsInternal)
};
wsInternal.onmessage = function (rawMessage) {
var message = JSON.parse(rawMessage.data);
console.log('got message: ', message)
console.log('got message: ', message)
listenEvents
.filter(listenedEvent => listenedEvent.event == message.event)
.forEach(listenedEvent => listenedEvent.callback(message.data))
};
wsInternal.onerror = function (errorEvent) {
console.error('WebSocket Error', errorEvent);
};
wsInternal.onclose = function (closeEvent) {
console.log('Closed WebSocket', closeEvent);
};
listenEvents
.filter(listenedEvent => listenedEvent.event == message.event)
.forEach(listenedEvent => listenedEvent.callback(message.data))
};
wsInternal.onerror = function (errorEvent) {
console.error('WebSocket Error', errorEvent);
};
wsInternal.onclose = function (closeEvent) {
console.log('Closed WebSocket', closeEvent);
};
webSocket = {
on: function (event, callback) {
console.log('registered ' + event + ' event');
listenEvents.push({ event: event, callback: callback })
},
emit: function (event, data) {
var message = {
event: event,
data: data
}
wsInternal.send(JSON.stringify(message));
webSocket = {
on: function (event, callback) {
console.log('registered ' + event + ' event');
listenEvents.push({ event: event, callback: callback })
},
emit: function (event, data) {
var message = {
event: event,
data: data
}
wsInternal.send(JSON.stringify(message));
}
} catch (error) {
console.error('Error while making websocket helpers', error);
usingWebSockets = false;
}
} catch (error) {
console.error('Error while making websocket helpers', error);
usingWebSockets = false;
}
// {% else %}
let usingWebSockets = false;
warn('WebSockets are not supported in Crafty if not using the https protocol')
var webSocket;
let usingWebSockets = false;
warn('WebSockets are not supported in Crafty if not using the https protocol')
var webSocket;
// {% end%}
if (webSocket) {
webSocket.on('send_start_error', function (start_error) {
var x = document.querySelector('.bootbox');
if(x){
x.remove()}
var x = document.querySelector('.modal-backdrop');
if(x){
x.remove()}
bootbox.alert({
message: start_error.error,
callback: function () {
location.reload();
}
})
});
}
if (webSocket) {
webSocket.on('send_logs_bootbox', function (server_id) {
var x = document.querySelector('.bootbox');
if(x){
x.remove()}
var x = document.querySelector('.modal-backdrop');
if(x){
x.remove()}
bootbox.alert({
title: 'Download Support Logs?',
message: "We've finished preparing your support logs. Please click download to download",
buttons: {
ok: {
label: 'Download',
className: 'btn-info'
if (webSocket) {
webSocket.on('send_start_error', function (start_error) {
var x = document.querySelector('.bootbox');
if (x) {
x.remove()
}
},
callback: function(){
console.log("in callback")
location.href="/panel/download_support_package";
}
});
});
}
if (webSocket) {
webSocket.on('send_eula_bootbox', function (server_id) {
var x = document.querySelector('.bootbox');
if(x){
x.remove()}
var x = document.querySelector('.modal-backdrop');
if(x){
x.remove()}
bootbox.confirm({
title: '{% raw translate("error", "eulaTitle", data['lang']) %}',
message: '{% raw translate("error", "eulaMsg", data['lang']) %} <br><br><a href="https://account.mojang.com/documents/minecraft_eula" target="_blank">EULA</a><br><br>{% raw translate("error", "eulaAgree", data['lang']) %}',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-info'
},
cancel: {
label: 'No',
className: 'btn-secondary'
var x = document.querySelector('.modal-backdrop');
if (x) {
x.remove()
}
},
callback: function (result) {
if(result == true){
eulaAgree(server_id.id)
}
else {
location.reload()
}
bootbox.alert({
message: start_error.error,
callback: function () {
location.reload();
}
})
});
}
if (webSocket) {
webSocket.on('send_logs_bootbox', function (server_id) {
var x = document.querySelector('.bootbox');
if (x) {
x.remove()
}
var x = document.querySelector('.modal-backdrop');
if (x) {
x.remove()
}
bootbox.alert({
title: "{{ translate('notify', 'downloadLogs', data['lang']) }}",
message: "{{ translate('notify', 'finishedPreparing', data['lang']) }}",
buttons: {
ok: {
label: 'Download',
className: 'btn-info'
}
},
callback: function () {
console.log("in callback")
location.href = "/panel/download_support_package";
}
});
});
}
})
});
}
function eulaAgree (server_id, command){
<!-- this getCookie function is in base.html-->
if (webSocket) {
webSocket.on('send_eula_bootbox', function (server_id) {
var x = document.querySelector('.bootbox');
if (x) {
x.remove()
}
var x = document.querySelector('.modal-backdrop');
if (x) {
x.remove()
}
bootbox.confirm({
title: '{% raw translate("error", "eulaTitle", data['lang']) %}',
message: '{% raw translate("error", "eulaMsg", data['lang']) %} <br><br><a href="https://account.mojang.com/documents/minecraft_eula" target="_blank">EULA</a><br><br>{% raw translate("error", "eulaAgree", data['lang']) %}',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-info'
},
cancel: {
label: 'No',
className: 'btn-secondary'
}
},
callback: function (result) {
if (result == true) {
eulaAgree(server_id.id)
}
else {
location.reload()
}
}
})
});
}
function eulaAgree(server_id, command) {
//< !--this getCookie function is in base.html-- >
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/eula?id='+ server_id,
success: function(data){
headers: { 'X-XSRFToken': token },
url: '/ajax/eula?id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
location.reload();
}
});
}
@ -332,7 +345,7 @@ if (webSocket) {
closeEl.style.lineHeight = '20px';
closeEl.style.cursor = 'pointer';
closeEl.addEventListener('click', function () {this.parentElement.style.display='none';});
closeEl.addEventListener('click', function () { this.parentElement.style.display = 'none'; });
var parentEl = document.createElement('div');
@ -360,7 +373,7 @@ if (webSocket) {
paragraphEl.textContent = message;
closeEl.innerHTML = '&times;';
closeEl.addEventListener('click', function () {closeNotification(this)});
closeEl.addEventListener('click', function () { closeNotification(this) });
var parentEl = document.createElement('div');
parentEl.appendChild(paragraphEl);
@ -388,12 +401,26 @@ if (webSocket) {
}
webSocket.on('notification', notify);
</script>
$(document).ready(function () {
$('#support_logs').click(function () {
var dialog = bootbox.dialog({
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i>{{ translate('notify', 'preparingLogs', data['lang']) }}</p>',
closeButton: false
});
setTimeout(function () {
location.href = "/panel/support_logs";
}, 6000);
{% block js %}
<!-- Custom js for this page -->
<!-- End custom js for this page -->
{% end %}
});
});
</script>
{% block js %}
<!-- Custom js for base.html page partial pages -->
<!-- End custom js for base.html page -->
{% end %}
</body>
</body>
</html>

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Blank Page{% end %}

View File

@ -18,22 +18,25 @@
<li class="nav-item dropdown user-dropdown">
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image"> </a>
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image"> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
<div class="dropdown-header text-center">
<img class="img-md rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image">
<img class="img-md rounded-circle profile-picture" src="{{ data['user_image'] }}" alt="Profile image">
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
<p class="font-weight-light text-muted mb-0">Roles: </p>
{% for r in data['user_role'] %}
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %}
{% if data.get('api_key') %}
<p class="mt-3">Logged in as API key "{{ data['api_key']['name'] }}"</p>
{% end %}
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div>
<a class="dropdown-item" href="/panel/support_logs"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i> Support Logs</i></a>
{% if "Super User" in data['user_role'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>
<a class="dropdown-item" id="support_logs" ><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}</i></a>
{% if data['superuser'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify', 'activityLog', data['lang']) }}</a>
{% end %}
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
</div>
</li>
</ul>

View File

@ -1,8 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Activity Logs{% end %}
@ -93,7 +91,7 @@ $( document ).ready(function() {
if($(window).width() < 1000){
$('.too_small').popover("show");
}
});
$(window).ready(function(){
$('body').click(function(){

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Contribute{% end %}
@ -73,7 +72,7 @@
<div class="media-body">
<p class="card-text">
Thank you for your interest in contributing to Aracdia Technology's Crafty Controller.
Thank you for your interest in contributing to Aracdia Technology's Crafty Controller.
We are always thinking of new ways for our community to contribute to this awesome project. <br><br> If you don't see
a contribution method that peaks your interest now please check back soon.
</p>

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Credits{% end %}

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<meta http-equiv="refresh" content="60">
{% end %}
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
@ -10,7 +9,7 @@
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
@ -29,16 +28,21 @@
<div class="col-lg-4 col-md-6">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}</h5>
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}
</h5>
<h3 class="mb-0 font-weight-semibold"> <i class="fas fa-chart-line"></i></h3>
</div>
<div class="wrapper my-auto ml-auto ml-lg-4">
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true" title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}" >
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{ data.get('hosts_data').get('cpu_usage') }}</span>
<p id="cpu_data" class="mb-0 text-success" data-toggle="tooltip" data-placement="top" data-html="true"
title="{% raw translate('dashboard', 'cpuCores', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cores') }} <br /> {% raw translate('dashboard', 'cpuCurFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_cur_freq') }} <br /> {% raw translate('dashboard', 'cpuMaxFreq', data['lang']) %}: {{ data.get('hosts_data').get('cpu_max_freq') }}">
{{ translate('dashboard', 'cpuUsage', data['lang']) }}: <span id="cpu_usage">{{
data.get('hosts_data').get('cpu_usage') }}</span>
</p>
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top" title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}" >
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{ data.get('hosts_data').get('mem_percent') }}%</span>
<p id="mem_usage" class="mb-0 text-danger" data-toggle="tooltip" data-placement="top"
title="{{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_usage') }}">
{{ translate('dashboard', 'memUsage', data['lang']) }}: <span id="mem_percent">{{
data.get('hosts_data').get('mem_percent') }}%</span>
</p>
</div>
</div>
@ -46,20 +50,24 @@
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}</h5>
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}
</h5>
<h3 class="mb-0 font-weight-semibold">{{ data['server_stats']['total'] }}</h3>
</div>
<div class="wrapper my-auto ml-auto ml-lg-4">
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online', data['lang']).lower() }}</p>
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard', 'offline', data['lang']).lower() }}</p>
<p class="mb-0 text-success">{{ data['server_stats']['running'] }} {{ translate('dashboard', 'online',
data['lang']).lower() }}</p>
<p class="mb-0 text-warning"> {{ data['server_stats']['stopped'] }} {{ translate('dashboard',
'offline', data['lang']).lower() }}</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}</h5>
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}
</h5>
<h3 class="mb-0 font-weight-semibold">{{ data['num_players'] }}</h3>
</div>
@ -79,24 +87,29 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-server"></i> &nbsp;{{ translate('dashboard', 'allServers', data['lang']) }}</h4>
<h4 class="card-title"><i class="fas fa-server"></i> &nbsp;{{ translate('dashboard', 'allServers',
data['lang']) }}</h4>
{% if len(data['servers']) > 0 %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
<span class="too_small" title="{{ translate('dashboard', 'cannotSeeOnMobile', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('dashboard', 'newServer', data['lang']) }}</a></div>
<div><a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('dashboard', 'newServer', data['lang']) }}</a></div>
</div>
<div class="card-body">
<div class="table-responsive">
{% if len(data['servers']) == 0%}
<div style="text-align: center; color: grey;">
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
<br>
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer', data['lang']) }}.</h7>
</div>
{% end %}
{% if len(data['servers']) > 0 %}
{% if len(data['servers']) == 0%}
<div style="text-align: center; color: grey;">
<h1>{{ translate('dashboard', 'welcome', data['lang']) }}</h1>
<br>
<h7>{{ translate('dashboard', 'no-servers', data['lang']) }} {{ translate('dashboard', 'newServer',
data['lang']) }}.</h7>
</div>
{% end %}
{% if len(data['servers']) > 0 %}
<table class="table table-hover">
<thead>
<tr class="rounded">
@ -121,24 +134,37 @@
<td id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<a class="stop_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'stop', data['lang']) }}> <i class="fas fa-stop"></i></a> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip" title={{ translate('dashboard', 'restart', data['lang']) }}> <i class="fas fa-sync"></i></a> &nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill', data['lang']) }}> <i class="fas fa-skull"></i></a> &nbsp;
{% elif server['stats']['updating']%}
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating', data['lang']) }}</i></a>
{% elif server['stats']['waiting_start']%}
<a data-id="{{server['server_data']['server_id']}}" class="" title={{ translate('dashboard', 'delay-explained', data['lang'])}}>{{ translate('dashboard', 'starting', data['lang']) }}</i></a>
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play" data-toggle="tooltip" title={{ translate('dashboard', 'start', data['lang']) }}></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone" data-toggle="tooltip" title={{ translate('dashboard', 'clone', data['lang']) }}></i></a>&nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip" title={{ translate('dashboard', 'kill', data['lang']) }}> <i class="fas fa-skull"></i></a> &nbsp;
{% end %}
{% if server['stats']['running'] %}
<a class="stop_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip"
title={{ translate('dashboard', 'stop' , data['lang']) }}> <i class="fas fa-stop"></i></a> &nbsp;
<a class="restart_button" data-id="{{server['server_data']['server_id']}}" data-toggle="tooltip"
title={{ translate('dashboard', 'restart' , data['lang']) }}> <i class="fas fa-sync"></i></a>
&nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title={{ translate('dashboard', 'kill' , data['lang']) }}> <i
class="fas fa-skull"></i></a> &nbsp;
{% elif server['stats']['updating']%}
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating',
data['lang']) }}</i></a>
{% elif server['stats']['waiting_start']%}
<a data-id="{{server['server_data']['server_id']}}" class="" title={{
translate('dashboard', 'delay-explained' , data['lang'])}}>{{ translate('dashboard', 'starting',
data['lang']) }}</i></a>
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button"><i class="fas fa-play"
data-toggle="tooltip" title={{ translate('dashboard', 'start' , data['lang']) }}></i></a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="clone_button"> <i class="fas fa-clone"
data-toggle="tooltip" title={{ translate('dashboard', 'clone' , data['lang']) }}></i></a>&nbsp;
<a class="kill_button" data-id="{{server['server_data']['server_id']}}" class="kill_button"
data-toggle="tooltip" title={{ translate('dashboard', 'kill' , data['lang']) }}> <i
class="fas fa-skull"></i></a> &nbsp;
{% end %}
{% end %}
</td>
<td>
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['cpu']}}">
<td id="server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
@ -147,13 +173,15 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</td>
<td>
<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="{{server['stats']['mem']}}">
<td id="server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
@ -162,38 +190,42 @@
{% else %}
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
{% if server['stats']['mem'] == 0 %}
0 MB
0 MB
{% else %}
{{server['stats']['mem']}}
{{server['stats']['mem']}}
{% end %}
</td>
<td>
<td id="server_world_{{server['server_data']['server_id']}}">
{{ server['stats']['world_name'] }} : {{ server['stats']['world_size'] }}
</td>
<td>
<td id="server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', data['lang']) }}<br />
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %}
{{ server['stats']['desc'] }} <br />
{% end %}
{% if server['stats']['desc'] != 'False' %}
{{ server['stats']['desc'] }} <br />
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
</td>
<td>
<td id="server_running_status_{{server['server_data']['server_id']}}">
{% if server['stats']['running'] %}
<i class="fas fa-thumbs-up"></i> <span class="text-success">{{ translate('dashboard', 'online', data['lang']) }}</span>
<i class="fas fa-thumbs-up"></i> <span class="text-success">{{ translate('dashboard', 'online',
data['lang']) }}</span>
{% else %}
<i class="fas fa-thumbs-down"></i> <span class="text-danger">{{ translate('dashboard', 'offline', data['lang']) }}</span>
<i class="fas fa-thumbs-down"></i> <span class="text-danger">{{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
</td>
</tr>
@ -212,192 +244,303 @@
</div>
<!-- content-wrapper ends -->
<style>
.popover-body{
color: white !important;;
}
.popover-body {
color: white !important;
;
}
</style>
{% end %}
{% block js %}
<script src="/static/assets/js/motd.js"></script>
<script>
$(document).ready(function(){
$('[data-toggle="popover"]').popover();
if($(window).width() < 1000){
$('.too_small').popover("show");
function display_motd() {
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
for (element of all_motds) {
initParser(element.id, element.id);
};
}
});
$(window).ready(function(){
$('body').click(function(){
$('.too_small').popover("hide");
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
});
$(window).ready(function () {
$('body').click(function () {
$('.too_small').popover("hide");
});
});
$(window).resize(function() {
// This will execute whenever the window is resized
if($(window).width() < 1000){
$('.too_small').popover("show");
}
else{
$('.too_small').popover("hide");
} // New width
});
});
$(window).resize(function () {
// This will execute whenever the window is resized
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
else {
$('.too_small').popover("hide");
} // New width
});
</script>
<script>
function send_command (server_id, command){
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
function send_command(server_id, command) {
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/server/command?command=' + command + '&id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){
if (command != 'start_server'){
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
/*setTimeout(function () {
if (command != 'start_server') {
location.reload();
}
}, 10000);
}, 10000);*/
}
});
}
function send_kill(server_id) {
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/kill?id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
/*setTimeout(function () {
location.reload();
}, 10000);*/
}
});
}
function update_one_server_status(server) {
server_cpu = document.getElementById('server_cpu_' + server.id);
server_mem = document.getElementById('server_mem_' + server.id);
server_world = document.getElementById('server_world_' + server.id);
server_desc = document.getElementById('server_desc_' + server.id);
server_online_status = document.getElementById('server_running_status_' + server.id);
console.log("Received Data : " + server.id + ": " + server);
/* TODO Update each element */
/* Update CPU */
cpu_status = "";
if (server.cpu <= 33)
{
cpu_status = "bg-success";
}
});
}
function send_kill (server_id){
/* this getCookie function is in base.html */
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/kill?id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){
location.reload();
}, 10000);
else if (server.cpu > 33 && server.cpu <= 66)
{
cpu_status = "bg-warning";
}
});
}
else
{
cpu_status = "bg-danger";
}
server_cpu.innerHTML = `<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="`+ server.cpu +`"><div class="progress-bar `+ cpu_status + `" role="progressbar" style="width: `+ server.cpu + `%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div>`+ server.cpu +`%`;
$( document ).ready(function() {
console.log('ready for JS!')
$( ".play_button" ).click(function() {
server_id = $(this).attr("data-id");
send_command(server_id, 'start_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientStart", data['lang']) %} </div>'
/* Update Memory */
mem_status = "";
total_mem = "";
if (server.mem_percent <= 33)
{
mem_status = "bg-success";
} else if (server.mem_percent > 33 && server.mem_percent <= 66)
{
mem_status = "bg-warning";
}
else
{
mem_status = "bg-danger";
}
if (server.mem == 0)
{
total_mem = "0 MB";
}
else
{
total_mem = server.mem;
}
server_mem.innerHTML = `<div class="progress mb-1" data-toggle="tooltip" data-placement="top" title="`+ server_mem +`"><div class="progress-bar `+ mem_status + `" role="progressbar" style="width: `+ server.mem_percent + `%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div>`+ server.mem_percent +`% - ` + total_mem;
/* Update World Infos */
server_world.innerHTML = server.world_name + ` : ` + server.world_size
/* Update Server Infos */
if (server.int_ping_results) {
/* Update Players */
if (server.players) {
server_desc.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
server_infos = "";
server_infos = server.online + " / " + server.max + "{{ translate('dashboard', 'max', data['lang']) }}<br />"
}
/* Update Motd */
var motd = "";
if (server.desc) {
motd = `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span>`;
server_infos = server_infos + motd + "<br />";
}
/* Version */
if (server.version) {
server_infos = server_infos + server.version
}
server_desc.innerHTML = server_infos;
}
/* Update Online Status */
var online_status = "";
if (server.running) {
online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`;
}
else {
online_status = `<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])}}</span>`;
}
server_online_status.innerHTML = online_status;
}
function update_servers_status(data) {
console.log(data);
for (server of data) {
update_one_server_status(server);
}
display_motd();
}
$(document).ready(function () {
console.log('ready for JS!')
$(".play_button").click(function () {
server_id = $(this).attr("data-id");
send_command(server_id, 'start_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientStart", data['lang']) %} </div>'
});
});
$( ".stop_button" ).click(function() {
console.log("stopping server");
server_id = $(this).attr("data-id");
send_command(server_id, 'stop_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientStop", data['lang']) %} </div>'
});
});
$( ".restart_button" ).click(function() {
server_id = $(this).attr("data-id");
send_command(server_id, 'restart_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientRestart", data['lang']) %} </div>'
$(".stop_button").click(function () {
console.log("stopping server");
server_id = $(this).attr("data-id");
send_command(server_id, 'stop_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientStop", data['lang']) %} </div>'
});
});
$( ".kill_button" ).click(function() {
});
$(".restart_button").click(function () {
server_id = $(this).attr("data-id");
send_command(server_id, 'restart_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientRestart", data['lang']) %} </div>'
});
});
$(".kill_button").click(function () {
server_id = $(this).attr("data-id");
bootbox.confirm({
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
buttons: {
confirm: {
message: "This will kill the server process and all it's subprocesses. Killing a process can potentially corrupt files. Only do this in extreme circumstances. Are you sure you would like to continue?",
buttons: {
confirm: {
label: '{% raw translate("dashboard", "kill", data['lang']) %}',
className: 'btn-danger'
},
cancel: {
},
cancel: {
label: '{% raw translate("panelConfig", "cancel", data['lang']) %}',
className: 'btn-secondary'
}
},
callback: function (result) {
if(result){
send_kill(server_id);
var dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing", data['lang']) %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
});
dialog.init(function(){
setTimeout(function(){
location.reload();
}, 15000);
});
}
}
});
});
if (webSocket) {
cpu_data = document.getElementById('cpu_data');
cpu_usage = document.getElementById('cpu_usage');
mem_usage = document.getElementById('mem_usage');
mem_percent = document.getElementById('mem_percent');
}
},
callback: function (result) {
if (result) {
send_kill(server_id);
var dialog = bootbox.dialog({
title: '{% raw translate("dashboard", "killing", data['lang']) %}',
message: '<p><i class="fa fa-spin fa-spinner"></i> Loading...</p>'
});
webSocket.on('update_host_stats', function (hostStats) {
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores', data['lang']) %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq", data['lang']) %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq", data['lang']) %}: ${hostStats.cpu_max_freq}`;
cpu_data.setAttribute('data-original-title', cpuDataTitle);
cpu_usage.textContent = hostStats.cpu_usage;
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
mem_percent.textContent = hostStats.mem_percent + '%';
dialog.init(function () {
setTimeout(function () {
location.reload();
}, 15000);
});
}
}
});
});
}
if (webSocket) {
cpu_data = document.getElementById('cpu_data');
cpu_usage = document.getElementById('cpu_usage');
mem_usage = document.getElementById('mem_usage');
mem_percent = document.getElementById('mem_percent');
webSocket.on('update_host_stats', function (hostStats) {
var cpuDataTitle = `{% raw translate('dashboard', 'cpuCores', data['lang']) %}: ${hostStats.cpu_cores} <br /> {% raw translate("dashboard", "cpuCurFreq", data['lang']) %}: ${hostStats.cpu_cur_freq} <br /> {% raw translate("dashboard", "cpuMaxFreq", data['lang']) %}: ${hostStats.cpu_max_freq}`;
cpu_data.setAttribute('data-original-title', cpuDataTitle);
cpu_usage.textContent = hostStats.cpu_usage;
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
mem_percent.textContent = hostStats.mem_percent + '%';
});
}
if (webSocket) {
webSocket.on('send_start_reload', function () {
location.reload()
});
}
webSocket.on('send_start_reload', function () {
location.reload()
});
}
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
var id = 'controls';
var dataId = updateButton.server_id;
var string = updateButton.string
var id = id.concat(updateButton.server_id);
if (updateButton.isUpdating){
console.log(updateButton.isUpdating)
document.getElementById(id).innerHTML = string;
}
else{
window.location.reload()
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
var id = 'controls';
var dataId = updateButton.server_id;
var string = updateButton.string
var id = id.concat(updateButton.server_id);
if (updateButton.isUpdating) {
console.log(updateButton.isUpdating)
document.getElementById(id).innerHTML = string;
}
});
}
else {
window.location.reload()
}
});
}
$( ".clone_button" ).click(function() {
server_id = $(this).attr("data-id");
send_command(server_id, 'clone_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data['lang']) %} </div>'
if (webSocket) {
webSocket.on('update_server_status', update_servers_status);
}
$(".clone_button").click(function () {
server_id = $(this).attr("data-id");
send_command(server_id, 'clone_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientClone", data['lang']) %} </div>'
});
});
});
});
</script>
{% end %}

View File

@ -1,10 +1,9 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Panel Config{% end %}
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
@ -14,7 +13,8 @@
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">Panel Config</h4>
<!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4>
</div>
</div>
@ -30,22 +30,23 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-users"></i> Users</h4>
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang']) }}</h4>
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; Add New User</a></div>
<!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('panelConfig', 'newUser', data['lang']) }}</a></div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<!-- TODO: Translate the following -->
<tr class="rounded">
<th>User</th>
<th>Enabled</th>
<th>API Token</th>
<th>Allowed Servers</th>
<th>Assigned Roles</th>
<th>Edit</th>
<th>{{ translate('panelConfig', 'user', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'enabled', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'allowedServers', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'assignedRoles', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'edit', data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -64,9 +65,6 @@
{% end %}
</td>
<td>
<button data-toggle="tooltip" title="Show API Key" data-id="{{ user.api_token }}" type="button" class="btn btn-info show_button">Show</button>
</td>
<td id="server_list_{{user.user_id}}">
<ul id="{{user.user_id}}">
{% for item in data['auth-servers'][user.user_id] %}
@ -95,19 +93,20 @@
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> Roles</h4>
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles', data['lang']) }}</h4>
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}", data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}", data-placement="top"></span>
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; Add New Role</a></div>
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('panelConfig', 'newRole', data['lang']) }}</a></div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<!-- TODO: Translate the following -->
<tr class="rounded">
<th>Role</th>
<th>Allowed Servers</th>
<th>Role Users</th>
<th>Edit</th>
<th>{{ translate('panelConfig', 'role', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'allowedServers', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'roleUsers', data['lang']) }}</th>
<th>{{ translate('panelConfig', 'edit', data['lang']) }}</th>
</tr>
</thead>
<tbody>

View File

@ -1,10 +1,9 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit Role{% end %}
{% block title %}Crafty Controller - {{ translate('rolesConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
@ -16,13 +15,13 @@
<div class="page-header">
{% if data['new_role'] %}
<h4 class="page-title">
New Role
{{ translate('rolesConfig', 'pageTitleNew', data['lang']) }}
<br />
<small>RID: N/A</small>
</h4>
{% else %}
<h4 class="page-title">
Edit Role - {{ data['role']['role_name'] }}
{{ translate('rolesConfig', 'pageTitle', data['lang']) }} - {{ data['role']['role_name'] }}
<br />
<small>RID: {{ data['role']['role_id'] }}</small>
</h4>
@ -41,7 +40,7 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
<i class="fas fa-cogs"></i>{{ translate('rolesConfig', 'config', data['lang']) }}</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" href="/panel/edit_role?id={{ data['role']['role_id'] }}&subpage=other" role="tab" aria-selected="false">
@ -58,22 +57,22 @@
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['role']['role_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> Role Settings</h4>
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('rolesConfig', 'roleTitle', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="form-group">
<label for="role_name">Role Name <small class="text-muted ml-1"> - What you wish to call this role</small> </label>
<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" >
</div>
</div>
</div>
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-server"></i> Allowed Servers <small class="text-muted ml-1"> - servers this role is allowed to access </small> </h4>
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('rolesConfig', 'roleServers', data['lang']) }} <small class="text-muted ml-1"> {{ translate('rolesConfig', 'serversDesc', data['lang']) }}</small> </h4>
</div>
<div class="card-body">
<div class="form-group">
@ -81,8 +80,8 @@
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>Server Name</th>
<th>Access?</th>
<th>{{ translate('rolesConfig', 'serverName', data['lang']) }}</th>
<th>{{ translate('rolesConfig', 'serverAccess', data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -105,19 +104,19 @@
</div>
</div>
</div>
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-lock"></i> Roles Permissions <small class="text-muted ml-1"> - permissions this role has on this/these servers </small></h4>
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('rolesConfig', 'rolePerms', data['lang']) }}<small class="text-muted ml-1"> - {{ translate('rolesConfig', 'permsServer', data['lang']) }} </small></h4>
</div>
<div class="card-body">
<div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>Permission Name</th>
<th>Authorized ?</th>
<th>{{ translate('rolesConfig', 'permName', data['lang']) }}</th>
<th>{{ translate('rolesConfig', 'permAccess', data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -149,14 +148,14 @@
<div class="col-md-3 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-users"></i> Users Assigned to Role:</h4>
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('rolesConfig', 'roleUsers', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>User Name</th>
<th>{{ translate('rolesConfig', 'roleUserName', data['lang']) }}</th>
<th></th>
</tr>
</thead>
@ -183,22 +182,22 @@
<div class="col-md-3 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">Role Config Area</h4>
<p class="card-description"> Here is where you can change the configuration of your role</p>
<h4 class="card-title">{{ translate('rolesConfig', 'roleConfigArea', data['lang']) }}</h4>
<p class="card-description"> {{ translate('rolesConfig', 'configDesc', data['lang']) }}</p>
<blockquote class="blockquote">
<p class="mb-0">
Created: {{ str(data['role']['created']) }}
{{ translate('rolesConfig', 'created', data['lang']) }} {{ str(data['role']['created']) }}
<br />
Last updated: {{ str(data['role']['last_update']) }}
{{ translate('rolesConfig', 'configUpdate', data['lang']) }} {{ str(data['role']['last_update']) }}
<br />
</p>
</blockquote>
<div class="text-center">
{% if data['new_role'] %}
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete Role</a><br />
<small>You cannot delete something that does not yet exist</small>
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a><br />
<small>{{ translate('rolesConfig', 'doesNotExist', data['lang']) }}</small>
{% else %}
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Delete Role</a>
<a href="/panel/remove_role?id={{ data['role']['role_id'] }}" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i>{{ translate('rolesConfig', 'delRole', data['lang']) }}</a>
{% end %}
</div>
</div>
@ -229,7 +228,6 @@
$( document ).ready(function() {
console.log( "ready!" );
});

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit User{% end %}
@ -16,13 +15,13 @@
<div class="page-header">
{% if data['new_user'] %}
<h4 class="page-title">
New User
{{ translate('userConfig', 'pageTitleNew', data['lang']) }}
<br />
<small>UID: N/A</small>
</h4>
{% else %}
<h4 class="page-title">
Edit User - {{ data['user']['user_id'] }}
{{ translate('userConfig', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }}
<br />
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
@ -41,12 +40,12 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/{{ 'add_user' if data['new_user'] else 'edit_user' }}?id={{ data['user']['user_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
<i class="fas fa-cogs"></i> {{ translate('userConfig', 'config', data['lang']) }} - {{ data['user']['user_id'] }}</a>
</li>
{% if not data['new_user'] %}
<li class="nav-item">
<a class="nav-link" href="/panel/add_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
<a class="nav-link" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab" aria-selected="false">
<i class="fas fa-key"></i>{{ translate('userConfig', 'apiKey', data['lang']) }} - {{ data['user']['user_id'] }}</a>
</li>
{% end %}
</ul>
@ -65,27 +64,27 @@
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user"></i> User Settings</h4>
<h4 class="card-title"><i class="fas fa-user"></i> {{ translate('userConfig', 'userSettings', data['lang']) }} - {{ data['user']['user_id'] }}</h4>
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
<label class="form-label" for="username">{{ translate('userConfig', 'userName', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'userNameDesc', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
</div>
<div class="form-group">
<label class="form-label" for="password0">Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
<label class="form-label" for="password0">{{ translate('userConfig', 'password', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
</div>
<div class="form-group">
<label class="form-label" for="password1">Repeat Password <small class="text-muted ml-1"> - leave blank to don't change</small> </label>
<label class="form-label" for="password1">{{ translate('userConfig', 'repeat', data['lang']) }} - {{ data['user']['user_id'] }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'leaveBlank', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
</div>
<div class="form-group">
<label class="form-label" for="email">Gravatar Email <small class="text-muted ml-1"> - for the profile picture. this is not required. crafty will never make use of user emails. User emails are strictly for Gravatar</small> </label>
<label class="form-label" for="email">{{ translate('userConfig', 'gravEmail', data['lang']) }} - {{ data['user']['user_id'] }}<small class="text-muted ml-1"> - {{ translate('userConfig', 'gravDesc', data['lang']) }} - {{ data['user']['user_id'] }}</small> </label>
<input type="email" class="form-control" name="email" id="email" value="{{ data['user']['email'] }}" placeholder="Gravatar Email" >
</div>
<div class="form-group">
<label class="form-label" for="language">User Language:</label>
<label class="form-label" for="language">{{ translate('userConfig', 'userLang', data['lang']) }}</label>
<select class="form-select form-control form-control-lg select-css" id="language" name="language" form="user_form">
{% for lang in data['languages'] %}
<option value="{{lang}}">{{lang}}</option>
@ -97,7 +96,7 @@
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-tag"></i> Roles <small class="text-muted ml-1"> - the roles this user is a member of</small></h4>
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('userConfig', 'userRoles', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'userRolesDesc', data['lang']) }}</small></h4>
</div>
<div class="card-body">
<div class="form-group">
@ -105,8 +104,8 @@
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>Role Name</th>
<th>Member?</th>
<th>{{ translate('userConfig', 'roleName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'member', data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -134,7 +133,7 @@
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-user-lock"></i> Crafty Permissions <small class="text-muted ml-1"> - permissions this user has on Crafty Controller </small></h4>
<h4 class="card-title"><i class="fas fa-user-lock"></i> {{ translate('userConfig', 'craftyPerms', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('userConfig', 'craftyPermDesc', data['lang']) }}permissions this user has on Crafty Controller </small></h4>
</div>
<div class="card-body">
<div class="form-group">
@ -142,9 +141,9 @@
<table class="table table-hover">
<thead>
<tr class="rounded">
<th>Permission Name</th>
<th>Authorized ?</th>
<th>Number of Uses Allowed (-1=No Limit)</th>
<th>{{ translate('userConfig', 'permName', data['lang']) }}</th>
<th>{{ translate('userConfig', 'auth', data['lang']) }}</th>
<th>{{ translate('userConfig', 'uses', data['lang']) }}</th>
</tr>
</thead>
<tbody>
@ -171,25 +170,17 @@
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
{% if data['user']['enabled'] %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">{{ translate('userConfig', 'enabled', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">Enabled
{% end %}
</label>
<label for="regen_api" class="form-check-label ml-4 mb-4">
{% if data['new_user'] %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" checked="" value="1" disabled >Regenerate API Key
{% else %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" value="1">Regenerate API Key
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">{{ translate('userConfig', 'enabled', data['lang']) }}
{% end %}
</label>
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['user']['superuser'] %}
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" checked="" value="1" {{ data['super-disabled'] }} >Super User
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" checked="" value="1" {{ data['super-disabled'] }} >{{ translate('userConfig', 'super', data['lang']) }}
{% else %}
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" {{ data['super-disabled'] }} value="1" >Super User
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" {{ data['super-disabled'] }} value="1" >{{ translate('userConfig', 'super', data['lang']) }}
{% end %}
</label>
@ -203,19 +194,17 @@
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title"><i class="fas fa-user-cog"></i> User Config Area</h4>
<p class="card-description"> Here is where you can change the configuration of your user</p>
<h4 class="card-title"><i class="fas fa-user-cog"></i> {{ translate('userConfig', 'configArea', data['lang']) }}</h4>
<p class="card-description"> {{ translate('userConfig', 'configAreaDesc', data['lang']) }}</p>
<blockquote class="blockquote">
<p class="mb-0">
Created: {{ str(data['user']['created']) }}
{{ translate('userConfig', 'created', data['lang']) }} {{ str(data['user']['created']) }}
<br />
Last login: {{ str(data['user']['last_login']) }}
{{ translate('userConfig', 'lastLogin', data['lang']) }} {{ str(data['user']['last_login']) }}
<br />
Last update: {{ str(data['user']['last_update']) }}
{{ translate('userConfig', 'lastUpdate', data['lang']) }} {{ str(data['user']['last_update']) }}
<br />
Last IP: {{ data['user']['last_ip'] }}
<br />
API Key: {{ data['user']['api_token'] }}
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
<br />
</p>
</blockquote>
@ -223,13 +212,13 @@
</div>
<div class="text-center">
{% if data['new_user'] %}
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
<small>You cannot delete something that does not yet exist</small>
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i>{{ translate('userConfig', 'deleteUserB', data['lang']) }}</a><br />
<small>{{ translate('userConfig', 'notExist', data['lang']) }}</small>
{% elif data['user']['superuser'] %}
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> Delete User</a><br />
<small>You cannot delete a superuser</small>
<a class="btn btn-sm btn-danger disabled"><i class="fas fa-trash"></i> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a><br />
<small>{{ translate('userConfig', 'delSuper', data['lang']) }}</small>
{% else %}
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> Delete User</a>
<button class="btn btn-sm btn-danger delete-user"><i class="fas fa-trash"></i> {{ translate('userConfig', 'deleteUserB', data['lang']) }}</a>
{% end %}
</div>
@ -256,8 +245,8 @@
console.log("User to delete is {{ data['user']['username'] }}");
bootbox.confirm({
title: "{% raw translate('usersConfig', 'deleteUser', data['lang']) %}"+"{{ data['user']['username'] }}",
message: "{{ translate('usersConfig', 'confirmDelete', data['lang']) }}",
title: "{% raw translate('userConfig', 'deleteUser', data['lang']) %}"+"{{ data['user']['username'] }}",
message: "{{ translate('userConfig', 'confirmDelete', data['lang']) }}",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'

View File

@ -0,0 +1,252 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - Edit User API Keys{% 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('apiKeys', 'pageTitle', data['lang']) }} - {{ data['user']['user_id'] }}
<br/>
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config"
role="tab"
aria-selected="false">
<i class="fas fa-cogs"></i>{{ translate('apiKeys', 'config', data['lang']) }}</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}"
role="tab"
aria-selected="true">
<i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</a>
</li>
</ul>
<div class="row">
<div class="col-md-7 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-key"></i>{{ translate('apiKeys', 'apiKeys', data['lang']) }}</h4>
</div>
<div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<!--<th>ID</th>-->
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for apikey in data['api_keys'] %}
<tr>
<!--<td>{-{ apikey.token_id }-}</td>-->
<td>{{ apikey.name }}</td>
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
<td>
{% if apikey.superuser %}
<span class="text-success">
<i class="fas fa-check-square"></i> {{ translate('apiKeys', 'yes', data['lang']) }}
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> {{ translate('apiKeys', 'no', data['lang']) }}
</span>
{% end %}
</td>
<td>{{ translate('apiKeys', 'server', data['lang']) }} {{ apikey.server_permissions }}
{{ translate('apiKeys', 'crafty', data['lang']) }} {{ apikey.crafty_permissions }}</td>
<td>
<button
class="btn btn-danger delete-api-key"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>{{ translate('panelConfig', 'delete', data['lang']) }}</button>
<button
class="btn btn-outline-primary get-a-token"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>{{ translate('apiKeys', 'getToken', data['lang']) }}
</button>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-5 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-plus"></i> {{ translate('apiKeys', 'createNew', data['lang']) }}</h4>
</div>
<div class="card-body">
<form id="user_form" class="forms-sample" method="post"
action="/panel/edit_user_apikeys">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<div class="form-group">
<label class="form-label" for="username">{{ translate('apiKeys', 'name', data['lang']) }}<small
class="text-muted ml-1"> - {{ translate('apiKeys', 'nameDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="name" id="name"
placeholder="API Key">
</div>
<table class="table table-hover mb-3">
<thead>
<tr class="rounded">
<th>{{ translate('apiKeys', 'permName', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'auth', data['lang']) }}</th>
</tr>
</thead>
<tbody>
{% for permission in data['server_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
{% for permission in data['crafty_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
</tbody>
</table>
<label for="superuser">Superuser</label>
<input type="checkbox" class="" id="superuser"
name="superuser" value="1">
<br/>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-plus"></i>
Create
</button>
<button type="reset" class="btn btn-light"><i
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$(document).ready(function () {
console.log("ready!");
$('.delete-api-key').click(function () {
var keyId = $(this).data("key-id");
var keyName = $(this).data("key-name");
bootbox.confirm({
title: `Remove API key ${keyName}?`,
message: "Do you want to delete this API key? This cannot be undone.",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> {{ translate("panelConfig", "cancel", data['lang']) }}'
},
confirm: {
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
}
},
callback: function (result) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/panel/remove_apikey?id=' + keyId,
success: function (data) {
location.reload();
},
});
}
});
})
$('.get-a-token').click(function () {
var keyId = $(this).data("key-id");
var keyName = $(this).data("key-name");
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/panel/get_token?id=' + keyId,
success: function (data) {
bootbox.alert({
title: `API token for ${keyName}`,
message: `Here is an API token for ${keyName}:\n<pre style="white-space: pre-wrap;color:white;word-break:break-all;background: grey;border-radius: 5px;">${data}</pre>`
});
},
});
})
});
</script>
{% end %}

View File

@ -1,56 +1,52 @@
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-3 pb-3">
<div class="row">
<div class="col-sm-4 mr-2">
{% if data['server_stats']['running'] %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
{% else %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span>
{% end %}
<br>
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
</div>
<div class="col-sm-3 mr-2">
<b>{{ translate('serverStats', 'cpuUsage', data['lang']) }}:</b> {{ data['server_stats']['cpu'] }}% <br />
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> {{ data['server_stats']['mem'] }} <br />
{% if data['server_stats']['int_ping_results'] %}
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}<br />
{% else %}
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> 0/0<br />
{% end %}
</div>
<div class="col-sm-3 mr-2">
{% if data['server_stats']['version'] != 'False' %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ data['server_stats']['version'] }} <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br />
{% else %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> {{ translate('serverStats', 'unableToConnect', data['lang']) }} <br />
{% end %}
</div>
</div>
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-3 pb-3">
<div class="row">
<div class="col-sm-4 mr-2">
{% if data['server_stats']['running'] %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-success">{{ translate('serverStats', 'online', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started">{{ data['server_stats']['started'] }} ({{ translate('serverStats', 'serverTime', data['lang']) }})</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime">{{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }}</span>
{% else %}
<b>{{ translate('serverStats', 'serverStatus', data['lang']) }}:</b> <span id="status" class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverStarted', data['lang']) }}:</b> <span id="started" class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span><br />
<b>{{ translate('serverStats', 'serverUptime', data['lang']) }}:</b> <span id="uptime" class="text-danger">{{ translate('serverStats', 'offline', data['lang']) }}</span>
{% end %}
<br>
<b>{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:</b> <span class="text-info">{{ data['serverTZ'] }}</span>
</div>
<div class="col-sm-3 mr-2">
<b>{{ translate('serverStats', 'cpuUsage', data['lang']) }}:</b> <span id="cpu">{{ data['server_stats']['cpu'] }}%</span> <br />
<b>{{ translate('serverStats', 'memUsage', data['lang']) }}:</b> <span id="mem" >{{ data['server_stats']['mem'] }}</span> <br />
{% if data['server_stats']['int_ping_results'] %}
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players" >{{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }}</span><br />
{% else %}
<b>{{ translate('serverStats', 'players', data['lang']) }}:</b> <span id="players" >0/0</span><br />
{% end %}
</div>
<div class="col-sm-3 mr-2">
{% if data['server_stats']['version'] != 'False' %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ data['server_stats']['version'] }}</span><br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ data['server_stats']['desc'] }}</span> <br />
{% else %}
<b>{{ translate('serverStats', 'version', data['lang']) }}:</b> <span id="version">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
<b>{{ translate('serverStats', 'description', data['lang']) }}:</b> <span id="input_motd" class="input_motd">{{ translate('serverStats', 'unableToConnect', data['lang']) }}</span> <br />
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/assets/vendors/moment/moment.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/assets/js/motd.js"></script>
<script>
function durationToHumanizedString (duration) {
function durationToHumanizedString(duration) {
duration._data.months += duration._data.years * 12;
// 30.45833333333 = average month length, calculate with (31+28.5+31+30+31+30+31+31+30+31+30+31) / 12
duration._data.days += duration._data.months * 30.45833333333;
@ -64,7 +60,7 @@
output = Object.entries(obj)
.map(([type, num]) => {
// make them strings
// make them strings
returnData = num + ' ' + type;
// remove the s in the end if the data is -1 or 1
if (num == -1 || num == 1)
@ -73,27 +69,28 @@
})
.map((v, i, a) => // example input: [1,2,3], output: "1, 2 and 3"
v + (i !== a.length - 1
? i !== a.length - 2
? i !== a.length - 2
? ', '
: ' and '
: '')).join('');
return output;
}
let uptime = document.querySelector('#uptime');
let started = document.querySelector('#started');
let startedUTC;
let startedLocal;
let uptimeLoop;
document.body.onload = (() => {
console.log('calculateTime');
let uptime = document.querySelector('#uptime');
let started = document.querySelector('#started');
let startedUTC;
let startedLocal;
if (started != null) {
startedUTC = '{{ data['server_stats']['started'] }}';
startedUTC = '{{ data['server_stats']['started'] }}';
if (startedUTC != 'False') {
console.log('started utc:', startedUTC);
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
let browserUTCOffset = moment().utcOffset(); // This is in minutes
var browserUTCOffset = moment().utcOffset(); // This is in minutes
startedLocal = startedUTC.utcOffset(browserUTCOffset);
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
@ -104,23 +101,108 @@
}
var calculateUptime = () => {
var msdiff = moment()
.diff(startedLocal);
var msdiff = moment().diff(startedLocal);
var diff = moment.duration(msdiff);
uptime.textContent = durationToHumanizedString(diff);
}
if (uptime != null && started != null) {
console.log('startedLocal', startedLocal)
if (startedLocal) {
calculateUptime()
var uptimeLoop = setInterval(calculateUptime, 1000)
calculateUptime();
uptimeLoop = setInterval(calculateUptime, 1000);
}
}
initParser('input_motd', 'input_motd');
});
function update_server_details(server) {
server_status = document.getElementById('status');
server_started = document.getElementById('started');
server_uptime = document.getElementById('uptime');
server_cpu = document.getElementById('cpu');
server_mem = document.getElementById('mem');
server_players = document.getElementById('players');
server_version = document.getElementById('version');
server_input_motd = document.getElementById('input_motd');
/* TODO Update each element */
if (server.running)
{
if (server.int_ping_results)
{
server_status.setAttribute("class", "text-success");
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
}
else
{
server_status.setAttribute("class", "text-warning");
server_status.innerHTML = `{{ translate('serverStats', 'starting', data['lang']) }}`;
}
startedUTC = server.started;
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
var browserUTCOffset = moment().utcOffset(); // This is in minutes
startedLocal = startedUTC.utcOffset(browserUTCOffset);
startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss');
server_started.setAttribute("class", "");
server_started.innerHTML = startedLocalFormatted +` ({{ translate('serverStats', 'serverTime', data['lang']) }})`;
server_uptime.setAttribute("class", "");
if (!uptimeLoop) {
var calculateUptime = () => {
var msdiff = moment().diff(startedLocal);
var diff = moment.duration(msdiff);
uptime.textContent = durationToHumanizedString(diff);
}
uptimeLoop = setInterval(calculateUptime, 1000);
}
}
else
{
server_status.setAttribute("class", "text-danger");
server_status.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
server_started.setAttribute("class", "text-danger");
server_started.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
clearInterval(uptimeLoop);
uptimeLoop = null;
server_uptime.setAttribute("class", "text-danger");
server_uptime.innerHTML = `{{ translate('serverStats', 'offline', data['lang']) }}`;
}
server_cpu.innerHTML = server.cpu + ` %`;
server_mem.innerHTML = server.mem;
if (server.int_ping_results)
{
server_players.innerHTML = server.online + `/` + server.max;
}
else
{
server_players.innerHTML = `0/0`;
}
if (server.version)
{
server_version.innerHTML = server.version;
server_input_motd.innerHTML = server.desc;
}
else
{
server_version.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
server_input_motd.innerHTML = `{{ translate('serverStats', 'unableToConnect', data['lang']) }}`;
}
initParser('input_motd', 'input_motd');
}
$(window).ready(function () {
console.log("ready!");
//if (webSocket) {
webSocket.on('update_server_details', update_server_details);
//}
});
</script>

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -33,7 +32,7 @@
<div class="card">
<div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
<div class="row">
<div class="col-md-6 col-sm-12">
<style>
@ -86,7 +85,7 @@
<li class="playerItem banned">
<h3>{{ translate('serverPlayerManagement', 'loadingBannedPlayers', data['lang']) }}</h3>
</li>
</ul>
</div>
</div>

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -65,7 +64,7 @@
<div class="card">
<div class="card-body">
<h4 class="card-title">{{ translate('serverBackups', 'currentBackups', data['lang']) }}</h4>
</div>
</div>
<div class="text-center">
@ -231,7 +230,7 @@
}
});
});
$( ".restore_button" ).click(function() {
var file_to_restore = $(this).data("file");

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -10,14 +9,15 @@
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<!-- 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>
{{ 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>
@ -25,132 +25,187 @@
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html" %}
{% include "parts/details_stats.html" %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
{% include "parts/server_controls_list.html %}
<div class="row">
<div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" action="/panel/server_detail">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="row">
<div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" action="/panel/server_detail">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="server_name" id="server_name" value="{{ data['server_stats']['server_id']['server_name'] }}" placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
</div>
<div class="form-group">
{% if data['super_user'] %}
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="server_path" id="server_path" value="{{ data['server_stats']['server_id']['path'] }}" placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="log_path" id="log_path" value="{{ data['server_stats']['server_id']['log_path'] }}" placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="executable" id="executable" value="{{ data['server_stats']['server_id']['executable'] }}" placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="execution_command" id="execution_command" value="{{ data['server_stats']['server_id']['execution_command'] }}" placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
{% end %}
</div>
<div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="stop_command" id="stop_command" value="{{ data['server_stats']['server_id']['stop_command'] }}" placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc', data['lang']) }}</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10" required>
</div>
{% if data['super_user'] %}
<div class="form-group">
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url" value="{{ data['server_stats']['server_id']['executable_update_url'] }}" placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
</div>
<div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="server_ip" id="server_ip" value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
</div>
<div class="form-group">
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }} </small> </label>
<input type="number" class="form-control" name="server_port" id="server_port" value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1" required>
</div>
{% end %}
<div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc', data['lang']) }}</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after" value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0" required>
</div>
<div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['auto_start'] %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked="" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% end %}
</label>
<label for="crash_detection" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['crash_detection'] %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection" value="1" >{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{ translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig', 'cancel', data['lang']) }}</button>
</form>
<div class="form-group">
<label for="server_name">{{ translate('serverConfig', 'serverName', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverNameDesc', data['lang']) }}</small>
</label>
<input type="text" class="form-control" name="server_name" id="server_name"
value="{{ data['server_stats']['server_id']['server_name'] }}"
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
</div>
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">{{ translate('serverConfigHelp', 'title', data['lang']) }}</h4>
<p class="card-description"> {{ translate('serverConfigHelp', 'desc', data['lang']) }}</p>
<blockquote class="blockquote">
<p class="mb-0">
{% raw translate('serverConfigHelp', 'perms', data['lang']) %}
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['server_stats']['running'] %}
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig', 'update', data['lang']) }}</button>
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</a><br />
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
{% else %}
<button onclick="send_command(server_id, 'update_executable');" id="update_executable" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig', 'update', data['lang']) }}</button>
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig', 'deleteServer', data['lang']) }}</button>
{% end %}
<div class="form-group">
{% if data['super_user'] %}
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
</label>
<input type="text" class="form-control" name="server_path" id="server_path"
value="{{ data['server_stats']['server_id']['path'] }}"
placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
</div>
</div>
<div class="form-group">
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="log_path" id="log_path"
value="{{ data['server_stats']['server_id']['log_path'] }}"
placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutableDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="executable" id="executable"
value="{{ data['server_stats']['server_id']['executable'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
data['lang']) }}</small> </label>
<input type="text" class="form-control" name="execution_command" id="execution_command"
value="{{ data['server_stats']['server_id']['execution_command'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
{% end %}
</div>
<div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverStopCommandDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="stop_command" id="stop_command"
value="{{ data['server_stats']['server_id']['stop_command'] }}"
placeholder="{{ translate('serverConfig', 'serverStopCommand', data['lang']) }}" required>
</div>
<div class="form-group">
<label for="auto_start_delay">{{ translate('serverConfig', 'serverAutostartDelay', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverAutostartDelayDesc',
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay"
value="{{ data['server_stats']['server_id']['auto_start_delay'] }}" step="1" max="999" min="10"
required>
</div>
{% if data['super_user'] %}
<div class="form-group">
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="executable_update_url" id="executable_update_url"
value="{{ data['server_stats']['server_id']['executable_update_url'] }}"
placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
</div>
<div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small
class="text-muted ml-1">- {{ translate('serverConfig', 'serverIPDesc', data['lang']) }}</small>
</label>
<input type="text" class="form-control" name="server_ip" id="server_ip"
value="{{ data['server_stats']['server_id']['server_ip'] }}" required>
</div>
<div class="form-group">
<label for="server_port">{{ translate('serverConfig', 'serverPort', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPortDesc', data['lang']) }}
</small> </label>
<input type="number" class="form-control" name="server_port" id="server_port"
value="{{ data['server_stats']['server_id']['server_port'] }}" step="1" max="65566" min="1"
required>
</div>
{% end %}
<div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'removeOldLogsAfterDesc',
data['lang']) }}</small> </label>
<input type="number" class="form-control" name="logs_delete_after" id="logs_delete_after"
value="{{ data['server_stats']['server_id']['logs_delete_after'] }}" step="1" max="365" min="0"
required>
</div>
<div class="form-check-flat">
<label for="auto_start" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['auto_start'] %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" checked=""
value="1">{{ translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="auto_start" name="auto_start" value="1">{{
translate('serverConfig', 'serverAutoStart', data['lang']) }}
{% end %}
</label>
<label for="crash_detection" class="form-check-label ml-4 mb-4">
{% if data['server_stats']['server_id']['crash_detection'] %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
checked="" value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="crash_detection" name="crash_detection"
value="1">{{ translate('serverConfig', 'serverCrashDetection', data['lang']) }}
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-save"></i> {{
translate('serverConfig', 'save', data['lang']) }}</button>
<button type="reset" class="btn btn-light"><i class="fas fa-times"></i> {{ translate('serverConfig',
'cancel', data['lang']) }}</button>
</form>
</div>
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">{{ translate('serverConfigHelp', 'title', data['lang']) }}</h4>
<p class="card-description"> {{ translate('serverConfigHelp', 'desc', data['lang']) }}</p>
<blockquote class="blockquote">
<p class="mb-0">
{% raw translate('serverConfigHelp', 'perms', data['lang']) %}
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['server_stats']['running'] %}
<button onclick="send_command(server_id, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button>
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang'])
}}</a><br />
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
{% else %}
<button onclick="send_command(server_id, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button>
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig',
'deleteServer', data['lang']) }}</button>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
@ -167,154 +222,154 @@
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
$(document).ready(function () {
console.log("ready!");
});
function deleteServerE(callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function (data) {
console.log("got response:");
console.log(data);
},
});
function deleteServerE(callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/delete_server?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
function deleteServerFilesE(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/delete_server_files?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function send_command (server_id, command){
<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/server/command?command=' + command + '&id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){ location.reload(); }, 10000);
}
});
if(command != "delete_server" && command != "delete_server_files"){
bootbox.alert({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingRequest", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientUpdate", data['lang']) %} </div>'
}
function deleteServerFilesE(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_server_files?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function (data) {
console.log("got response:");
console.log(data);
},
});
}
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function send_command(server_id, command) {
//<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
setTimeout(function () { location.reload(); }, 10000);
}
});
if (command != "delete_server" && command != "delete_server_files") {
bootbox.alert({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingRequest", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientUpdate", data['lang']) %} </div>'
});
}
}
function deleteServer (){
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.dialog({
size: "",
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
buttons: {
files: {
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
className: 'btn-danger',
callback: function(){
deleteServerFilesE();
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
closeButton: false
})
function deleteServer() {
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.dialog({
size: "",
title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}",
buttons: {
files: {
label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}",
className: 'btn-danger',
callback: function () {
deleteServerFilesE();
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
closeButton: false
})
return;
}
},
noFiles: {
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
className: 'btn-outline-danger',
callback: function(){
deleteServerE()
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
closeButton: false
})
return;
}
},
cancel: {
label: "{{ translate('serverConfig', 'cancel', data['lang']) }}",
className: 'btn-secondary',
callback: function(){
return;
}
return;
}
},
callback: function(result) {
},
noFiles: {
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
className: 'btn-outline-danger',
callback: function () {
deleteServerE()
setTimeout(function () { window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
closeButton: false
})
return;
}
});
}
function deleteConfirm (){
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.confirm({
size: "",
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
buttons: {
confirm: {
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
className: 'btn-danger',
},
cancel: {
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
className: 'btn-link',
}
},
callback: function(result) {
if (!result){
return;
return;}
else{
deleteServer();
}
},
cancel: {
label: "{{ translate('serverConfig', 'cancel', data['lang']) }}",
className: 'btn-secondary',
callback: function () {
return;
}
});
}
}
},
callback: function (result) {
}
});
}
function deleteConfirm() {
path = "{{data['server_stats']['server_id']['path']}}";
name = "{{data['server_stats']['server_id']['server_name']}}";
bootbox.confirm({
size: "",
title: "{% raw translate('serverConfig', 'deleteServerQuestion', data['lang']) %}",
closeButton: false,
message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data['lang']) %}",
buttons: {
confirm: {
label: "{{ translate('serverConfig', 'yesDelete', data['lang']) }}",
className: 'btn-danger',
},
cancel: {
label: "{{ translate('serverConfig', 'noDelete', data['lang']) }}",
className: 'btn-link',
}
},
callback: function (result) {
if (!result) {
return;
return;
}
else {
deleteServer();
}
}
});
}
</script>

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -557,7 +556,7 @@
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
@ -637,7 +636,7 @@
}
});
var fileList = document.getElementById("files");
fileList.addEventListener("change", function (e) {
@ -672,7 +671,7 @@
}catch{
document.getElementById('files-tree').innerHTML = text;
}
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
@ -724,7 +723,7 @@
setTimeout(function () {setTreeViewContext()}, 1000);
var toggler = document.getElementById(path);
if (toggler.classList.contains('files-tree-title')){
document.getElementById(path+"span").addEventListener("click", function caretListener() {
document.getElementById(path+"ul").classList.toggle("d-block");

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -203,9 +202,9 @@ try{
}catch{
console.log("no element named {{ data['schedule']['action'] }}")
}
ifDays();
yesnoCheck();
basicAdvanced();
ifDays();
if("{{ data['schedule']['enabled'] }}" == 'True'){
document.getElementById('enabled').checked = true;
}else{

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -205,7 +204,7 @@
</div>
<style>
/* Hide scrollbar for Chrome, Safari and Opera */
td::-webkit-scrollbar {
display: none;
@ -246,7 +245,7 @@ $( document ).ready(function() {
document.getElementById('schedule_table_wrapper').hidden = true;
document.getElementById('mini_schedule_table_wrapper').hidden = false;
}
});
$(window).ready(function(){
$('body').click(function(){

View File

@ -1,7 +1,6 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
@ -10,14 +9,14 @@
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<!-- 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>
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
</div>
</div>
@ -25,66 +24,67 @@
</div>
<!-- Page Title Header Ends-->
{% include "parts/details_stats.html %}
{% include "parts/details_stats.html %}
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
{% include "parts/server_controls_list.html %}
<div class="col-md-12">
<div class="input-group">
<div id="virt_console" class="" style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
</div>
<br />
{% include "parts/server_controls_list.html %}
<div style="gap: 0.5rem;" class="input-group flex-wrap">
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
<span class="input-group-btn ml-5">
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', data['lang']) }}</button>
</span>
</div>
{% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['server_stats']['updating']%}
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% elif data['waiting_start'] %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% else %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% end %}
{% end %}
<div class="col-md-12">
<div class="input-group">
<div id="virt_console" class="" style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid #383e5d; background-color:#2a2c44;height:500px; overflow: scroll;"></div>
</div>
<br />
<div style="gap: 0.5rem;" class="input-group flex-wrap">
<input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus="">
<span class="input-group-btn ml-5">
<button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand',
data['lang']) }}</button>
</span>
</div>
{% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['server_stats']['updating']%}
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% elif data['waiting_start'] %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% else %}
<div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="send_command(server_id, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button>
<button onclick="send_command(server_id, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="send_command(server_id, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>
{% end %}
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
<style>
#virt_console::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
#virt_console {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#virt_console::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
#virt_console {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
</style>
<!-- content-wrapper ends -->
@ -93,188 +93,182 @@
{% block js %}
<script>
function send_command (server_id, command){
if (command == 'start_server'){
startBtn.setAttribute('disabled', 'disabled');
restartBtn.removeAttribute('disabled');
stopBtn.removeAttribute('disabled');
}
if (command == 'stop_server'){
startBtn.removeAttribute('disabled');
restartBtn.setAttribute('disabled', 'disabled');
stopBtn.setAttribute('disabled', 'disabled');
}
<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/server/command?command=' + command + '&id=' + server_id,
success: function(data){
console.log("got response:");
console.log(data);
setTimeout(function(){
if (command != 'start_server'){
location.reload();
}
}, 10000);
}
});
}
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
if (updateButton.isUpdating){
if(updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
console.log(updateButton.isUpdating)
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data['lang']) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
else{
if (updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data['lang']) }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
});
}
// Convert running to lower case (example: 'True' converts to 'true') and
// then to boolean via JSON.parse()
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
let startBtn = document.querySelector('#start-btn');
let restartBtn = document.querySelector('#restart-btn');
let stopBtn = document.querySelector('#stop-btn');
{% if data['permissions']['Commands'] in data['user_permissions'] %}
if (online) {
function send_command(server_id, command) {
if (command == 'start_server') {
startBtn.setAttribute('disabled', 'disabled');
restartBtn.removeAttribute('disabled');
stopBtn.removeAttribute('disabled');
} else {
}
if (command == 'stop_server') {
startBtn.removeAttribute('disabled');
restartBtn.setAttribute('disabled', 'disabled');
stopBtn.setAttribute('disabled', 'disabled');
}
{% end %}
//<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function get_server_log(){
$.ajax({
type: 'GET',
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}',
dataType: 'text',
success: function (data) {
console.log('Got Log From Server')
$('#virt_console').html(data);
scrollConsole();
},
});
}
function new_line_handler(data) {
$('#virt_console').append(data.line)
const elem = document.getElementById('virt_console');
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
scrollConsole()
}
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
get_server_log()
webSocket.on('vterm_new_line', new_line_handler)
});
$('#server_command').on('keydown', function (e) {
if (e.which == 13){
$(this).attr("disabled", "disabled"); //Disable textbox to prevent multiple submit
send_command_to_server()
$(this).removeAttr("disabled"); //Enable the textbox again if needed.
$(this).focus();
}
else if (e.which == 38) {
e.preventDefault();
$('#server_command').val(cmdHistory.getPrev());
} else if (e.which == 40) {
e.preventDefault();
$('#server_command').val(cmdHistory.getNext());
}
});
$("#submit").click(function(e) {
e.preventDefault();
send_command_to_server();
});
function scrollConsole(){
var logview = $('#virt_console');
if(logview.length)
logview.scrollTop(logview[0].scrollHeight - logview.height());
}
function send_command_to_server(){
var server_command = $("#server_command").val()
console.log(server_command)
cmdHistory.push(server_command);
var token = getCookie("_xsrf")
data_to_send = { command :server_command, }
console.log('sending command: ' + server_command)
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/send_command?id={{ data['server_stats']['server_id']['server_id'] }}',
data: data_to_send,
success: function(data){
console.log("got response:");
console.log(data);
$("#server_command").val('')
},
});
}
const cmdHistory = {
history: [],
current: 0,
push: function(cmd) {
this.history.push(cmd);
this.current = this.history.length - 1;
},
getPrev: function() {
const prevCommand = this.history[this.current];
this.current--;
if (this.current < 0) this.current = 0;
return prevCommand;
},
getNext: function() {
this.current++;
if (this.current > (this.history.length - 1)) {
this.current = (this.history.length - 1);
return '';
}
const nextCommand = this.history[this.current];
return nextCommand;
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/server/command?command=' + command + '&id=' + server_id,
success: function (data) {
console.log("got response:");
console.log(data);
}
});
}
if (webSocket) {
webSocket.on('update_button_status', function (updateButton) {
if (updateButton.isUpdating) {
if (updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
console.log(updateButton.isUpdating)
document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data['lang']) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
else {
if (updateButton.server_id == '{{ data['server_stats']['server_id']['server_id'] }}') {
window.location.reload()
document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(server_id, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data['lang']) }}</button><button onclick="send_command(server_id, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data['lang']) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data['lang']) }}</button>';
}
}
});
}
// Convert running to lower case (example: 'True' converts to 'true') and
// then to boolean via JSON.parse()
let online = JSON.parse('{{ data['server_stats']['running'] }}'.toLowerCase());
let startBtn = document.querySelector('#start-btn');
let restartBtn = document.querySelector('#restart-btn');
let stopBtn = document.querySelector('#stop-btn');
{% if data['permissions']['Commands'] in data['user_permissions'] %}
if (online) {
startBtn.setAttribute('disabled', 'disabled');
restartBtn.removeAttribute('disabled');
stopBtn.removeAttribute('disabled');
} else {
startBtn.removeAttribute('disabled');
restartBtn.setAttribute('disabled', 'disabled');
stopBtn.setAttribute('disabled', 'disabled');
}
{% end %}
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function get_server_log() {
$.ajax({
type: 'GET',
url: '/ajax/server_log?id={{ data['server_stats']['server_id']['server_id'] }}',
dataType: 'text',
success: function (data) {
console.log('Got Log From Server')
$('#virt_console').html(data);
scrollConsole();
},
});
}
function new_line_handler(data) {
$('#virt_console').append(data.line)
const elem = document.getElementById('virt_console');
const scrollDiff = (elem.scrollHeight - elem.scrollTop) - elem.clientHeight;
if (!$("#stop_scroll").is(':checked') && scrollDiff < 450) {
scrollConsole()
}
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$(document).ready(function () {
console.log("ready!");
get_server_log()
webSocket.on('vterm_new_line', new_line_handler)
});
$('#server_command').on('keydown', function (e) {
if (e.which == 13) {
$(this).attr("disabled", "disabled"); //Disable textbox to prevent multiple submit
send_command_to_server()
$(this).removeAttr("disabled"); //Enable the textbox again if needed.
$(this).focus();
}
else if (e.which == 38) {
e.preventDefault();
$('#server_command').val(cmdHistory.getPrev());
} else if (e.which == 40) {
e.preventDefault();
$('#server_command').val(cmdHistory.getNext());
}
});
$("#submit").click(function (e) {
e.preventDefault();
send_command_to_server();
});
function scrollConsole() {
var logview = $('#virt_console');
if (logview.length)
logview.scrollTop(logview[0].scrollHeight - logview.height());
}
function send_command_to_server() {
var server_command = $("#server_command").val()
console.log(server_command)
cmdHistory.push(server_command);
var token = getCookie("_xsrf")
data_to_send = { command: server_command, }
console.log('sending command: ' + server_command)
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/send_command?id={{ data['server_stats']['server_id']['server_id'] }}',
data: data_to_send,
success: function (data) {
console.log("got response:");
console.log(data);
$("#server_command").val('')
},
});
}
const cmdHistory = {
history: [],
current: 0,
push: function (cmd) {
this.history.push(cmd);
this.current = this.history.length - 1;
},
getPrev: function () {
const prevCommand = this.history[this.current];
this.current--;
if (this.current < 0) this.current = 0;
return prevCommand;
},
getNext: function () {
this.current++;
if (this.current > (this.history.length - 1)) {
this.current = (this.history.length - 1);
return '';
}
const nextCommand = this.history[this.current];
return nextCommand;
}
}
</script>

View File

@ -1,7 +1,6 @@
{% extends ../public_base.html %}
{% block meta %}
<meta http-equiv="refresh" content="30">
{% end %}
{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard', data['lang']) }}{% end %}
@ -24,39 +23,49 @@
<tbody>
{% for server in data['servers'] %}
<tr>
<td>
<td id="server_name_{{ server['stats']['server_id']['server_id'] }}">
<i class="fas fa-server"></i>
{{ server['server_data']['server_name'] }}
{{ server['server_data']['server_name'] }}
</td>
{% if server['stats']['int_ping_results'] != 'False' %}
<td>
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max', data['lang']) }}<br />
</td>
<td>
{% if server['stats']['desc'] != 'False' %}
{% if server['raw_ping_result']['icon'] %}
<img src="data:image/png;base64,{% raw server['raw_ping_result']['icon'] %}" alt="icon"/>
{% else %}
<img src="/static/assets/images/pack.png" alt="icon" />
{% end %}
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{ server['stats']['desc'] }}</span> <br />
{% end %}
</td>
<td>
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% if server['stats']['int_ping_results'] != 'False' %}
<td id="server_players_{{ server['stats']['server_id']['server_id'] }}">
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard', 'max',
data['lang']) }}<br />
</td>
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
{% if server['stats']['desc'] != 'False' %}
{% if server['raw_ping_result']['icon'] %}
<img src="data:image/png;base64,{% raw server['raw_ping_result']['icon'] %}" alt="icon" />
{% else %}
<td colspan="3">
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Crafty can't get infos from this Server </span>
</td>
<img src="/static/assets/images/pack.png" alt="icon" />
{% end %}
<td>
<span id="input_motd_{{ server['stats']['server_id']['server_id'] }}" class="input_motd">{{
server['stats']['desc'] }}</span> <br />
{% end %}
</td>
<td id="server_version_{{ server['stats']['server_id']['server_id'] }}">
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
</td>
{% else %}
<td id="server_players_{{ server['stats']['server_id']['server_id'] }}">
<span class="text-warning"><i class="fas fa-exclamation-triangle"></i></span>
</td>
<td id="server_motd_{{ server['stats']['server_id']['server_id'] }}">
<span class="text-warning">Crafty can't get infos from this Server </span>
</td>
<td id="server_version_{{ server['stats']['server_id']['server_id'] }}">
<span class="text-warning"><i class="fas fa-question"></i></i></span>
</td>
{% end %}
<td id="server_online_status_{{ server['stats']['server_id']['server_id'] }}">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang']) }}</span>
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])
}}</span>
{% else %}
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang']) }}</span>
<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])
}}</span>
{% end %}
</td>
</tr>
@ -72,14 +81,83 @@
{% block js %}
<script src="/static/assets/js/motd.js"></script>
<script>
$(document).ready(function () {
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
for (element of all_motds) {
initParser(element.id, element.id);
};
}());
</script>
<script src="/static/assets/js/motd.js"></script>
<script>
function display_motd() {
var all_motds = Array.from(document.getElementsByClassName('input_motd'));
for (element of all_motds) {
initParser(element.id, element.id);
};
}
function update_one_server_status(server) {
server_players = document.getElementById('server_players_' + server.id);
server_motd = document.getElementById('server_motd_' + server.id);
server_version = document.getElementById('server_version_' + server.id);
server_online_status = document.getElementById('server_online_status_' + server.id);
/* TODO Update each element */
if (server.int_ping_results) {
/* Update Players */
if (server.players)
{
server_players.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}<br />`
}
/* Update Motd */
var motd = "";
if (server.desc) {
if (server.icon) {
motd = `<img src="data:image/png;base64,` + server.icon + `" alt="icon" /> `;
}
else {
motd = `<img src="/static/assets/images/pack.png" alt="icon" /> `;
}
motd = motd + `<span id="input_motd_` + server.id + `" class="input_motd">` + server.desc + `</span> <br />`;
server_motd.innerHTML = motd;
}
/* Version */
if (server.version)
{
server_version.innerHTML = server.version
}
}
else {
server_players.innerHTML = `<span class="text-warning"><i class="fas fa-exclamation-triangle"></i></span>`;
server_motd.innerHTML = `<span class="text-warning">Crafty can't get infos from this Server </span>`;
server_version.innerHTML = `<span class="text-warning"><i class="fas fa-question"></i></i></span>`
}
/* Update Online Status */
var online_status = "";
if (server.running) {
online_status = `<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online', data['lang'])}}</span>`;
}
else {
online_status = `<span class="text-danger"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline', data['lang'])}}</span>`;
}
server_online_status.innerHTML = online_status;
}
function update_servers_status(data) {
for (server of data) {
update_one_server_status(server);
}
display_motd();
}
$(document).ready(function () {
console.log("ready!");
display_motd()
if (webSocket)
{
webSocket.on('update_server_status', update_servers_status);
}
}());
</script>
{% end %}

View File

@ -1,60 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block meta %}{% end %}
<title>{% block title %}{{ _('Default') }}{% end %}</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="mx-auto">
<div class="auto-form-wrapper">
{% block content %}
{% end %}
</div>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block meta %}{% end %}
<title>{% block title %}{{ _('Default') }}{% end %}</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<link rel="stylesheet" href="/static/assets/vendors/fontawesome5/css/all.css">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="mx-auto">
<div class="auto-form-wrapper">
{% block content %}
{% end %}
</div>
</div>
</div>
<!-- content-wrapper ends -->
</div>
<!-- page-body-wrapper ends -->
<!-- content-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
{% block js %}
<!-- Custom js for this page -->
<!-- End custom js for this page -->
{% end %}
</body>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
<script>
// {% if request.protocol == 'https' %}
let usingWebSockets = true;
let listenEvents = [];
try {
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
page = 'page=' + encodeURIComponent(location.pathname)
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
wsInternal.onopen = function () {
console.log('opened WebSocket connection:', wsInternal)
};
wsInternal.onmessage = function (rawMessage) {
var message = JSON.parse(rawMessage.data);
console.log('got message: ', message)
listenEvents
.filter(listenedEvent => listenedEvent.event == message.event)
.forEach(listenedEvent => listenedEvent.callback(message.data))
};
wsInternal.onerror = function (errorEvent) {
console.error('WebSocket Error', errorEvent);
};
wsInternal.onclose = function (closeEvent) {
console.log('Closed WebSocket', closeEvent);
};
webSocket = {
on: function (event, callback) {
console.log('registered ' + event + ' event');
listenEvents.push({ event: event, callback: callback })
},
emit: function (event, data) {
var message = {
event: event,
data: data
}
wsInternal.send(JSON.stringify(message));
}
}
} catch (error) {
console.error('Error while making websocket helpers', error);
usingWebSockets = false;
}
// {% else %}
let usingWebSockets = false;
warn('WebSockets are not supported in Crafty if not using the https protocol')
var webSocket;
// {% end%}
</script>
{% block js %}
<!-- Custom js for this page -->
<!-- End custom js for this page -->
{% end %}
</body>
</html>

View File

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

View File

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

View File

@ -122,6 +122,7 @@
"serverStats": {
"online": "Online",
"offline": "Offline",
"starting": "Verzögerter Start",
"serverStatus": "Server Status",
"serverStarted": "Server gestartet",
"serverUptime": "Server-Betriebszeit",
@ -342,5 +343,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Warnung: </strong>Crafty funktioniert nicht richtig, wenn JavaScript nicht aktiviert ist!"
},
"apiKeys": {
"deleteKeyConfirmation": "Diesen API Schlüssel entfernen? Dies kann nicht rückgängig gemacht werden.",
"deleteKeyConfirmationTitle": "Folgenden API Schlüssel entfernen ${keyId}?"
}
}

View File

@ -76,10 +76,6 @@
"clickRoot": "Click here to select Root Dir",
"explainRoot": "Please click the button below to select your server's root dir inside of the archive"
},
"usersConfig":{
"deleteUser": "Delete user: ",
"confirmDelete": "Are you sure you want to delete this user? This action is irreversible."
},
"dashboard": {
"dashboard": "Dashboard",
"memUsage": "Memory Usage",
@ -133,6 +129,7 @@
"serverStats": {
"online": "Online",
"offline": "Offline",
"starting": "Delayed-Start",
"serverStatus": "Server Status",
"serverStarted": "Server Started",
"serverUptime": "Server Uptime",
@ -173,14 +170,21 @@
"loadingBannedPlayers": "Loading Banned Players"
},
"serverSchedules":{
"areYouSure": "Deleted Scheduled Task?",
"areYouSure": "Delete Scheduled Task?",
"confirmDelete": "Do you want to delete this scheduled task? This cannot be undone.",
"cancel": "Cancel",
"confirm": "Confirm",
"cannotSee": "Not seeing everything?",
"cannotSeeOnMobile": "Try clicking on a scheduled task for full details."
},
"notify":{
"supportLogs": "Support Logs",
"activityLog": "Activity Logs",
"logout": "Logout",
"preparingLogs": " Please wait while we prepare your logs... We`ll send a notification when they`re ready. This may take a while for large deployments.",
"downloadLogs": "Download Support Logs?",
"finishedPreparing": "We've finished preparing your support logs. Please click download to download"
},
"serverBackups": {
"backupNow": "Backup Now!",
"backupAtMidnight": "Auto-backup at midnight?",
@ -297,12 +301,84 @@
]
},
"panelConfig": {
"pageTitle": "Panel Config",
"users": "Users",
"roles": "Roles",
"newUser": "Add New User",
"newRole": "Add New Role",
"user": "User",
"enabled": "Enabled",
"allowedServers": "Allowed Servers",
"assignedRoles": "Assigned Roles",
"edit": "Edit",
"role": "Role",
"roleUsers": "Role Users",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"superConfirmTitle": "Enable Super User? Are you sure?",
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel configs, etc). They can even remove your super user access."
"superConfirmTitle": "Enable superuser? Are you sure?",
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel settings, etc.). They can even revoke your superuser rights."
},
"userConfig": {
"pageTitle": "Edit User",
"pageTitleNew": "Create User",
"config": "Config",
"apiKey": "API Keys",
"userSettings": "User Settings",
"userName": "User Name",
"userNameDesc": "What do you want to call this user?",
"password": "Password",
"repeat": "Repeat Password",
"leaveBlank": "To edit user without changing password leave it blank.",
"craftyPermDesc": "Crafty permissions this user has ",
"gravEmail": "Gravatar™ Email",
"gravDesc": "This email is strictly for use with Gravatar™. Crafty will not, under any circumstance make use of this email for anything other than looking up your Gravatar™",
"userLang": "User Language",
"userRoles": "User Roles",
"userRolesDesc": "Roles this user is a member of.",
"roleName": "Role Name",
"member": "Member?",
"craftyPerms": "Crafty Permissons: ",
"permName":"Permission Name",
"auth": "Authorized? ",
"uses": "Number of uses allowed (-1==No Limit)",
"super": "Super User",
"enabled": "Enabled",
"configArea": "User Config Area",
"configAreaDesc": "Here is where you change all of your user settings",
"created": "Created: ",
"lastLogin": "Last Login: ",
"lastUpdate": "Last Update: ",
"lastIP": "Last IP: ",
"deleteUserB": "Delete User",
"notExist": "You cannot delete something that doesn't exist!",
"delSuper": "You cannot delete a super user",
"deleteUser": "Delete user: ",
"confirmDelete": "Are you sure you want to delete this user? This action is irreversible."
},
"rolesConfig": {
"pageTitle": "Edit Role",
"pageTitleNew": "New Role",
"config": "Role Config",
"roleTitle": "Roles Settings",
"roleName": "Role Name: ",
"roleDesc": "What would you like to call this role?",
"roleServers": "Allowed Servers",
"serversDesc": "servers this role is allowed to access",
"serverName": "Server Name",
"serverAccess": "Access?",
"rolePerms": "Role Permissions",
"permsServer": "Permissions this role has for these specified servers",
"permName": "Permission Name",
"permAccess": "Access?",
"roleUsers": "Role Users: ",
"roleUserName": "User Name",
"roleConfigArea": "Role Config Area",
"configDesc": "Here is where you can change the configuration of your role",
"created": "Created: ",
"configUpdate": "Last Updated: ",
"delRole": "Delete Role",
"doesNotExist": "You cannot delete something that does not exist yet"
},
"datatables": {
"i18n": {
@ -370,5 +446,27 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Warning: </strong>Crafty doesn't work properly when JavaScript isn't enabled!"
},
"apiKeys": {
"pageTitle": "Edit User API Keys",
"config": "Config",
"apiKeys": "API Keys",
"name": "Name",
"created": "Created",
"perms": "Permissions",
"buttons": "Buttons",
"yes": "Yes",
"no": "No",
"server": "Server: ",
"crafty": "Crafty: ",
"getToken": "Get A Token",
"createNew": "Create new API Token",
"nameDesc": "What would you like to call this API token? ",
"permName": "Permission Name",
"auth":"Authorized? ",
"superUser": "Super User",
"deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.",
"deleteKeyConfirmationTitle": "Remove API key ${keyId}?"
}
}

View File

@ -123,6 +123,7 @@
"serverStats": {
"online": "En línea",
"offline": "Desconectado",
"starting": "Inicio-retrasado",
"serverStatus": "Estado del Servidor",
"serverStarted": "Servidor Iniciado",
"serverUptime": "Actividad del Servidor",
@ -346,5 +347,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Aviso: </strong>¡Crafty no funciona correctamente cuando JavaScript no está habilitado!"
},
"apiKeys": {
"deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.",
"deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?"
}
}

View File

@ -12,7 +12,7 @@
"embarassing": "No, tämähän on noloa.",
"error": "Virhe!",
"start-error": "Palvelin {} ei käynnistynyt virhekoodilla: {}",
"closedPort": "Olemme havainneet, että portti {} ei ehkä ole auki isäntäverkossa tai palomuuri estää sen. Etäasiakkaan yhteydet palvelimeen voivat olla rajallisia.",
"portReminder": "Olemme havainneet, että tämä on ensimmäinen kerta, kun {} on käynnistetty. Varmista, että välität porttia {} reitittimesi/palomuurisi kautta, jotta se on käytössä internetistä.",
"internet": "Olemme havainneet, että Crafty -koneella ei ole Internet -yhteyttä. Asiakasyhteydet palvelimelle voivat olla rajalliset.",
"eulaTitle": "Hyväksy EULA",
"eulaMsg": "Sinun on hyväksyttävä EULA. Kopio Mojang EULA:sta on linkitetty tämän viestin alla.",
@ -68,8 +68,17 @@
"downloading": "Lataamme palvelinta...",
"addRole": "Lisää Palvelin Olemassa Oleviin Rooleihin",
"autoCreate": "Jos ketään ei valita, Crafty tekee sellaisen!",
"selectRole": "Valitse roolit"
"selectRole": "Valitse roolit",
"selectZipDir": "Valitse arkistosta hakemisto, josta haluat meidän purkavan tiedostot",
"close": "Sulje",
"save": "Tallenna",
"selectRoot": "Valitse arkiston päähakemisto",
"clickRoot": "Napsauta tästä valitaksesi juurihakemiston",
"explainRoot": "Napsauta alla olevaa painiketta valitaksesi palvelimesi juurihakemiston arkistosta"
},
"usersConfig":{
"deleteUser": "Poista käyttäjä: ",
"confirmDelete": "Oletko varma, että haluat poistaa tämän käyttäjän? Tätä ei voi peruuttaa."
},
"dashboard": {
"dashboard": "Kojelauta",
@ -124,6 +133,7 @@
"serverStats": {
"online": "Päällä",
"offline": "Pois päältä",
"starting": "Myöhästynyt lähtö",
"serverStatus": "Palvelimen tila",
"serverStarted": "Palvelin käynnistyi",
"serverUptime": "Palvelimen käyttöaika",
@ -134,7 +144,8 @@
"description": "Kuvaus",
"errorCalculatingUptime": "Virhe laskettaessa käyttöaikaa",
"serverTime": "UTC aikaa",
"unableToConnect": "Yhteyden muodostaminen epäonnistui"
"unableToConnect": "Yhteyden muodostaminen epäonnistui",
"serverTimeZone": "Palvelimen aikavyöhyke"
},
"serverDetails": {
"serverDetails": "Palvelimen tiedot",
@ -162,6 +173,14 @@
"bannedPlayers": "Kielletyt pelaajat",
"loadingBannedPlayers": "Ladataan kiellettyjen pelaajien listaa"
},
"serverSchedules":{
"areYouSure": "Poista ajoitettu tehtävä?",
"confirmDelete": "Haluatko poistaa tämän ajoitetun tehtävän? Tätä ei voi peruuttaa.",
"cancel": "Peruuta",
"confirm": "Vahvista",
"cannotSee": "Etkö näe kaikkea?",
"cannotSeeOnMobile": "Napsauta ajoitettua tehtävää saadaksesi täydet tiedot."
},
"serverBackups": {
"backupNow": "Varmuuskopioi nyt!",
"backupAtMidnight": "Automaattisesti varmuuskopioi keskiyöllä?",
@ -206,14 +225,15 @@
"unsupportedLanguage": "Varoitus: Tätä tiedostotyyppiä ei tueta",
"keybindings": "Pikanäppäimet",
"fileReadError": "Tiedoston lukuvirhe",
"upload": "Lataa",
"upload": "Lähetä",
"unzip": "Pura",
"clickUpload": "Valitse tiedostosi napsauttamalla tätä",
"uploadTitle": "Lähetä tiedostot: ",
"waitUpload": "Odota, kunnes lataamme tiedostosi ... Tämä voi kestää hetken.",
"stayHere": "ÄLÄ JÄTÄ SIVUTA!",
"close": "Kiinni",
"download": "Ladata"
"waitUpload": "Odota, kun lähetämme tiedostojasi... Tämä voi kestää hetken.",
"stayHere": "ÄLÄ POISTU SIVULTA!",
"close": "Sulje",
"download": "Lataa",
"loadingRecords": "Ladataan tiedostoja..."
},
"serverConfig": {
"serverName": "Palvelimen nimi",
@ -248,12 +268,12 @@
"bePatientUpdate": "Ole kärsivällinen, kun päivitämme palvelinta. Latausajat voivat vaihdella Internet-nopeutesi mukaan.<br /> Tämä näyttö päivittyy hetkessä",
"sendingRequest": "Pyyntöäsi lähetetään...",
"deleteServerQuestion": "Poistetaanko palvelin?",
"deleteServerQuestionMessage": "Haluatko varmasti poistaa tämän palvelimen? Tämän jälkeen ei ole paluuta...",
"deleteServerQuestionMessage": "Haluatko varmasti poistaa tämän palvelimen? Tätä ei voi peruuttaa...",
"yesDelete": "Kyllä, poista",
"noDelete": "Ei, mene takaisin",
"deleteFilesQuestion": "Poistetaanko palvelintiedostot koneelta?",
"deleteFilesQuestionMessage": "Haluatko Craftyn poistavan kaikki palvelintiedostot isäntäkoneelta? <br><br><strong> Tämä sisältää palvelimen varmuuskopiot. <strong>",
"yesDeleteFiles": "Kyllä, poista tiedostoja",
"yesDeleteFiles": "Kyllä, poista tiedostot",
"noDeleteFiles": "Ei, poista vain paneelista",
"sendingDelete": "Poistetaan palvelinta",
"bePatientDelete": "Ole kärsivällinen, kun poistamme palvelimesi Crafty-paneelista. Tämä näyttö sulkeutuu hetken kuluttua.",
@ -279,7 +299,9 @@
"panelConfig": {
"save": "Tallenna",
"cancel": "Peruuta",
"delete": "Poista"
"delete": "Poista",
"superConfirmTitle": "Otetaanko järjestelmänvalvojan oikeudet käyttöön? Oletko varma?",
"superConfirm": "Jatka vain, jos haluat, että tällä käyttäjällä on pääsy KAIKKEEN (kaikki käyttäjätilit, palvelimet, paneelin asetukset jne.). Hän voi jopa poistaa sinun järjestelmänvalvojan oikeutesi."
},
"datatables": {
"i18n": {
@ -371,5 +393,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Varoitus: </strong>Crafty ei toimi kunnolla ilman JavaScriptiä!"
},
"apiKeys": {
"deleteKeyConfirmation": "Haluatko varmasti poistaa tämän API-avaimen? Tämä on peruuttamaton toimenpide!",
"deleteKeyConfirmationTitle": "Poistetaanko API-avain ${keyId}?"
}
}

View File

@ -123,6 +123,7 @@
"serverStats": {
"online": "En Ligne",
"offline": "Hors Ligne",
"starting": "Démarrage en cours",
"serverStatus": "Statut du Serveur",
"serverStarted": "Serveur Démarré",
"serverUptime": "Serveur Disponible depuis",
@ -346,5 +347,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Attention: </strong>Crafty ne fonctionne pas correctement si JavaScript n'est pas activé !"
},
"apiKeys": {
"deleteKeyConfirmation": "Es-tu sûr de vouloir supprimer cette clé API? Tu ne pourras plus revenir en arrière.",
"deleteKeyConfirmationTitle": "Supprimer la clé API ${keyId}?"
}
}

View File

@ -122,6 +122,7 @@
"serverStats": {
"online": "Na mreži",
"offline": "Izvan mreže",
"starting": "Odgođeno pokretanje",
"serverStatus": "Status poslužitelja",
"serverStarted": "Poslužitelj pokrenut",
"serverUptime": "Vrijeme rada poslužitelja",
@ -342,5 +343,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Upozorenje: </strong>Crafty ne radi ispravno kada JavaScript nije omogućen!"
},
"apiKeys": {
"deleteKeyConfirmation": "Želite li izbrisati ovaj API ključ? Ova radnja je nepovratna.",
"deleteKeyConfirmationTitle": "Ukloniti API ključ ${keyId}?"
}
}

View File

@ -122,6 +122,7 @@
"serverStats": {
"online": "Online",
"offline": "Offline",
"starting": "Avvio ritardato",
"serverStatus": "Stato del server",
"serverStarted": "Server Avviato",
"serverUptime": "Tempo di esecuzione",

View File

@ -123,6 +123,7 @@
"serverStats": {
"online": "Turnd on",
"offline": "Turnd off",
"starting": "I waitz b4 I start",
"serverStatus": "Servr Status",
"serverStarted": "Servr Started",
"serverUptime": "How longz servr been awaek",

View File

@ -122,6 +122,7 @@
"serverStats": {
"online": "Online",
"offline": "Offline",
"starting": "Vertraagde start",
"serverStatus": "Server Status",
"serverStarted": "Server gestart",
"serverUptime": "Server Uptime",

View File

@ -123,6 +123,7 @@
"serverStats": {
"online": "运行中",
"offline": "已停止",
"starting": "延迟启动",
"serverStatus": "服务器状态",
"serverStarted": "服务器已启动",
"serverUptime": "服务器正常运行时间",
@ -346,5 +347,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>警告:</strong>Crafty 无法在没有 JavaScript 的情况下使用!"
},
"apiKeys": {
"deleteKeyConfirmation": "您确定要删除该 API 密钥吗?此操作无法撤销。",
"deleteKeyConfirmationTitle": "删除 API 密钥 ${keyId}"
}
}

View File

@ -6,13 +6,20 @@ upstream crafty {
}
server {
listen 80 default_server;
listen 80;
server_name <DOMAIN>;
if ($host !~* ^<SUBDOMAIN>\.<EXAMPLE>\.com$ ) {
return 444;
}
rewrite ^(.*) https://$host$1 permanent;
}
server {
listen 443 ssl;
server_name <DOMAIN>;
if ($host !~* ^<SUBDOMAIN>\.<EXAMPLE>\.com$ ) {
return 444;
}
ssl_certificate <CERIFICATE_LOCATION>;
ssl_certificate_key <KEYFILE_LOCATION>;
location / {

View File

@ -1,18 +1,19 @@
cryptography~=3.4
argon2-cffi~=20.1
bleach~=3.1
colorama~=0.4
cryptography~=3.4
libgravatar~=1.0.0
peewee~=3.13
pexpect~=4.8
psutil~=5.7
pyOpenSSL~=19.1.0
cryptography==3.4
argon2-cffi==20.1
bleach==3.1
colorama==0.4
cryptography==3.4
libgravatar==1.0.0
peewee==3.13
pexpect==4.8
psutil==5.7
pyOpenSSL==19.1.0
PyYAML==5.3.1
requests~=2.26
termcolor~=1.1
tornado~=6.0
requests==2.26
termcolor==1.1
tornado==6.0
cached_property==1.5.2
apscheduler~=3.8.1
cron-validator~=1.0.3
tzlocal~=4.1
apscheduler==3.8.1
cron-validator==1.0.3
tzlocal==4.0
pyjwt==2.3