Merge branch 'dev' into enhancement/crafty-loading
12
CHANGELOG.md
@ -2,10 +2,18 @@
|
|||||||
## --- [4.2.2] - 2023/TBD
|
## --- [4.2.2] - 2023/TBD
|
||||||
### New features
|
### New features
|
||||||
TBD
|
TBD
|
||||||
|
### Refactor
|
||||||
|
- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670))
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
TBD
|
- Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664))
|
||||||
### Tweaks
|
### Tweaks
|
||||||
TBD
|
- Homogenize Panel logos/branding ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/666))
|
||||||
|
- Retain previous tab when revisiting server details page (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
|
||||||
|
- Add server name tag in panel header (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
|
||||||
|
- Setup logging for panel authentication attempts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
|
||||||
|
- Update minimum password length from 6 to 8, and unrestrict maximum password length ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
|
||||||
|
- Fix Unban button failing to pardon users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/671))
|
||||||
|
- Fix stack in API error handling ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/674))
|
||||||
### Lang
|
### Lang
|
||||||
TBD
|
TBD
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -38,6 +38,7 @@ class ServersController(metaclass=Singleton):
|
|||||||
self.servers_list = []
|
self.servers_list = []
|
||||||
self.stats = Stats(self.helper, self)
|
self.stats = Stats(self.helper, self)
|
||||||
self.ws = WebSocketManager()
|
self.ws = WebSocketManager()
|
||||||
|
self.server_subpage = {}
|
||||||
|
|
||||||
# **********************************************************************************
|
# **********************************************************************************
|
||||||
# Generic Servers Methods
|
# Generic Servers Methods
|
||||||
|
@ -45,8 +45,7 @@ class UsersController:
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"maxLength": 20,
|
"minLength": 8,
|
||||||
"minLength": 6,
|
|
||||||
"examples": ["crafty"],
|
"examples": ["crafty"],
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
},
|
},
|
||||||
|
@ -78,6 +78,37 @@ class Controller:
|
|||||||
self.first_login = False
|
self.first_login = False
|
||||||
self.cached_login = self.management.get_login_image()
|
self.cached_login = self.management.get_login_image()
|
||||||
self.support_scheduler.start()
|
self.support_scheduler.start()
|
||||||
|
try:
|
||||||
|
with open(
|
||||||
|
os.path.join(os.path.curdir, "logs", "auth_tracker.log"),
|
||||||
|
"r",
|
||||||
|
encoding="utf-8",
|
||||||
|
) as f:
|
||||||
|
self.auth_tracker = json.load(f)
|
||||||
|
except:
|
||||||
|
self.auth_tracker = {}
|
||||||
|
|
||||||
|
def log_attempt(self, remote_ip, username):
|
||||||
|
remote = self.auth_tracker.get(str(remote_ip), None)
|
||||||
|
if remote:
|
||||||
|
remote["names"].append(username)
|
||||||
|
remote["attempts"] += 1
|
||||||
|
remote["times"].append(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||||
|
self.auth_tracker[str(remote_ip)] = remote
|
||||||
|
else:
|
||||||
|
self.auth_tracker[str(remote_ip)] = {
|
||||||
|
"names": [username],
|
||||||
|
"attempts": 1,
|
||||||
|
"times": [datetime.now().strftime("%d/%m/%Y %H:%M:%S")],
|
||||||
|
}
|
||||||
|
|
||||||
|
def write_auth_tracker(self):
|
||||||
|
with open(
|
||||||
|
os.path.join(os.path.curdir, "logs", "auth_tracker.log"),
|
||||||
|
"w",
|
||||||
|
encoding="utf-8",
|
||||||
|
) as f:
|
||||||
|
json.dump(self.auth_tracker, f, indent=4)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_system_user():
|
def check_system_user():
|
||||||
|
@ -201,6 +201,13 @@ class TasksManager:
|
|||||||
id="update_watcher",
|
id="update_watcher",
|
||||||
start_date=datetime.datetime.now(),
|
start_date=datetime.datetime.now(),
|
||||||
)
|
)
|
||||||
|
self.scheduler.add_job(
|
||||||
|
self.controller.write_auth_tracker,
|
||||||
|
"interval",
|
||||||
|
minutes=5,
|
||||||
|
id="auth_tracker_write",
|
||||||
|
start_date=datetime.datetime.now(),
|
||||||
|
)
|
||||||
# self.scheduler.add_job(
|
# self.scheduler.add_job(
|
||||||
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
|
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
|
||||||
# )
|
# )
|
||||||
|
@ -1,446 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
|
|
||||||
from app.classes.controllers.crafty_perms_controller import EnumPermissionsCrafty
|
|
||||||
from app.classes.controllers.server_perms_controller import EnumPermissionsServer
|
|
||||||
from app.classes.web.base_handler import BaseHandler
|
|
||||||
from app.classes.models.management import DatabaseShortcuts
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
class ApiHandler(BaseHandler):
|
|
||||||
def return_response(self, status: int, data: dict):
|
|
||||||
# Define a standardized response
|
|
||||||
self.set_status(status)
|
|
||||||
self.write(data)
|
|
||||||
|
|
||||||
def check_xsrf_cookie(self):
|
|
||||||
# Disable CSRF protection on API routes
|
|
||||||
pass
|
|
||||||
|
|
||||||
def access_denied(self, user, reason=""):
|
|
||||||
if reason:
|
|
||||||
reason = " because " + reason
|
|
||||||
logger.info(
|
|
||||||
"User %s from IP %s was denied access to the API route "
|
|
||||||
+ self.request.path
|
|
||||||
+ reason,
|
|
||||||
user,
|
|
||||||
self.get_remote_ip(),
|
|
||||||
)
|
|
||||||
self.finish(
|
|
||||||
self.return_response(
|
|
||||||
403,
|
|
||||||
{
|
|
||||||
"error": "ACCESS_DENIED",
|
|
||||||
"info": "You were denied access to the requested resource",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def authenticate_user(self) -> bool:
|
|
||||||
self.permissions = {
|
|
||||||
"Commands": EnumPermissionsServer.COMMANDS,
|
|
||||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
|
||||||
"Logs": EnumPermissionsServer.LOGS,
|
|
||||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
|
||||||
"Backup": EnumPermissionsServer.BACKUP,
|
|
||||||
"Files": EnumPermissionsServer.FILES,
|
|
||||||
"Config": EnumPermissionsServer.CONFIG,
|
|
||||||
"Players": EnumPermissionsServer.PLAYERS,
|
|
||||||
"Server_Creation": EnumPermissionsCrafty.SERVER_CREATION,
|
|
||||||
"User_Config": EnumPermissionsCrafty.USER_CONFIG,
|
|
||||||
"Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
logger.debug("Searching for specified token")
|
|
||||||
|
|
||||||
api_token = self.get_argument("token", "")
|
|
||||||
self.api_token = api_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)
|
|
||||||
|
|
||||||
logger.debug("Checking results")
|
|
||||||
if user_data:
|
|
||||||
# Login successful! Check perms
|
|
||||||
logger.info(f"User {user_data['username']} has authenticated to API")
|
|
||||||
|
|
||||||
return True # This is to set the "authenticated"
|
|
||||||
logging.debug("Auth unsuccessful")
|
|
||||||
self.access_denied("unknown", "the user provided an invalid token")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning("An error occured while authenticating an API user: %s", e)
|
|
||||||
self.finish(
|
|
||||||
self.return_response(
|
|
||||||
403,
|
|
||||||
{
|
|
||||||
"error": "ACCESS_DENIED",
|
|
||||||
"info": "An error occured while authenticating the user",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ServersStats(ApiHandler):
|
|
||||||
def get(self):
|
|
||||||
"""Get details about all servers"""
|
|
||||||
authenticated = self.authenticate_user()
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
if not authenticated:
|
|
||||||
return
|
|
||||||
if user_obj["superuser"]:
|
|
||||||
raw_stats = self.controller.servers.get_all_servers_stats()
|
|
||||||
else:
|
|
||||||
raw_stats = self.controller.servers.get_authorized_servers_stats(
|
|
||||||
user_obj["user_id"]
|
|
||||||
)
|
|
||||||
stats = []
|
|
||||||
for rs in raw_stats:
|
|
||||||
s = {}
|
|
||||||
for k, v in rs["server_data"].items():
|
|
||||||
if isinstance(v, datetime):
|
|
||||||
s[k] = v.timestamp()
|
|
||||||
else:
|
|
||||||
s[k] = v
|
|
||||||
stats.append(s)
|
|
||||||
|
|
||||||
# Get server stats
|
|
||||||
# TODO Check perms
|
|
||||||
self.finish(self.write({"servers": stats}))
|
|
||||||
|
|
||||||
|
|
||||||
class NodeStats(ApiHandler):
|
|
||||||
def get(self):
|
|
||||||
"""Get stats for particular node"""
|
|
||||||
authenticated = self.authenticate_user()
|
|
||||||
if not authenticated:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get node stats
|
|
||||||
node_stats = self.controller.servers.stats.get_node_stats()
|
|
||||||
self.return_response(200, {"code": node_stats["node_stats"]})
|
|
||||||
|
|
||||||
|
|
||||||
class SendCommand(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
|
|
||||||
if (
|
|
||||||
not user_obj["user_id"]
|
|
||||||
in self.controller.server_perms.get_server_user_list(server_id)
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"Commands"
|
|
||||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
return
|
|
||||||
|
|
||||||
command = self.get_argument("command", default=None, strip=True)
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
if command:
|
|
||||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
if server.check_running:
|
|
||||||
server.send_command(command)
|
|
||||||
self.return_response(200, {"run": True})
|
|
||||||
else:
|
|
||||||
self.return_response(200, {"error": "SER_NOT_RUNNING"})
|
|
||||||
else:
|
|
||||||
self.return_response(200, {"error": "NO_COMMAND"})
|
|
||||||
|
|
||||||
|
|
||||||
class ServerBackup(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
|
|
||||||
if (
|
|
||||||
not user_obj["user_id"]
|
|
||||||
in self.controller.server_perms.get_server_user_list(server_id)
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"Backup"
|
|
||||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
return
|
|
||||||
|
|
||||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
|
|
||||||
server.backup_server()
|
|
||||||
|
|
||||||
self.return_response(200, {"code": "SER_BAK_CALLED"})
|
|
||||||
|
|
||||||
|
|
||||||
class StartServer(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
remote_ip = self.get_remote_ip()
|
|
||||||
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
|
|
||||||
if (
|
|
||||||
not user_obj["user_id"]
|
|
||||||
in self.controller.server_perms.get_server_user_list(server_id)
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
if not self.permissions[
|
|
||||||
"Commands"
|
|
||||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
|
|
||||||
if not server.check_running():
|
|
||||||
self.controller.management.send_command(
|
|
||||||
user_obj["user_id"], server_id, remote_ip, "start_server"
|
|
||||||
)
|
|
||||||
self.return_response(200, {"code": "SER_START_CALLED"})
|
|
||||||
else:
|
|
||||||
self.return_response(500, {"error": "SER_RUNNING"})
|
|
||||||
|
|
||||||
|
|
||||||
class StopServer(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
remote_ip = self.get_remote_ip()
|
|
||||||
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
|
|
||||||
if (
|
|
||||||
not user_obj["user_id"]
|
|
||||||
in self.controller.server_perms.get_server_user_list(server_id)
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"Commands"
|
|
||||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
return
|
|
||||||
|
|
||||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
|
||||||
|
|
||||||
if server.check_running():
|
|
||||||
self.controller.management.send_command(
|
|
||||||
user, server_id, remote_ip, "stop_server"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.return_response(200, {"code": "SER_STOP_CALLED"})
|
|
||||||
else:
|
|
||||||
self.return_response(500, {"error": "SER_NOT_RUNNING"})
|
|
||||||
|
|
||||||
|
|
||||||
class RestartServer(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
remote_ip = self.get_remote_ip()
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
server_id = self.get_argument("id")
|
|
||||||
|
|
||||||
if not user_obj["user_id"] in self.controller.server_perms.get_server_user_list(
|
|
||||||
server_id
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"Commands"
|
|
||||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
|
|
||||||
self.controller.management.send_command(
|
|
||||||
user, server_id, remote_ip, "restart_server"
|
|
||||||
)
|
|
||||||
self.return_response(200, {"code": "SER_RESTART_CALLED"})
|
|
||||||
|
|
||||||
|
|
||||||
class CreateUser(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
|
|
||||||
user_obj["user_id"]
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
not self.permissions["User_Config"] in user_perms
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"User_Config"
|
|
||||||
] in self.controller.crafty_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token)
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
return
|
|
||||||
|
|
||||||
new_username = self.get_argument("username").lower()
|
|
||||||
new_pass = self.get_argument("password")
|
|
||||||
manager = int(user_obj["user_id"])
|
|
||||||
|
|
||||||
if new_username:
|
|
||||||
self.controller.users.add_user(
|
|
||||||
new_username, manager, new_pass, "default@example.com", True, False
|
|
||||||
)
|
|
||||||
|
|
||||||
self.return_response(
|
|
||||||
200,
|
|
||||||
{
|
|
||||||
"code": "COMPLETE",
|
|
||||||
"username": new_username,
|
|
||||||
"password": new_pass,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.return_response(
|
|
||||||
500,
|
|
||||||
{
|
|
||||||
"error": "MISSING_PARAMS",
|
|
||||||
"info": "Some paramaters failed validation",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteUser(ApiHandler):
|
|
||||||
def post(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
|
|
||||||
user_obj["user_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
not self.permissions["User_Config"] in user_perms
|
|
||||||
and not user_obj["superuser"]
|
|
||||||
):
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.permissions[
|
|
||||||
"User_Config"
|
|
||||||
] in self.controller.crafty_perms.get_api_key_permissions_list(
|
|
||||||
self.controller.users.get_api_key_by_token(self.api_token)
|
|
||||||
):
|
|
||||||
self.access_denied(user)
|
|
||||||
return
|
|
||||||
|
|
||||||
user_id = self.get_argument("user_id", None, True)
|
|
||||||
user_to_del = self.controller.users.get_user_by_id(user_id)
|
|
||||||
|
|
||||||
if user_to_del["superuser"]:
|
|
||||||
self.return_response(
|
|
||||||
500,
|
|
||||||
{"error": "NOT_ALLOWED", "info": "You cannot delete a super user"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if user_id:
|
|
||||||
self.controller.users.remove_user(user_id)
|
|
||||||
self.return_response(200, {"code": "COMPLETED"})
|
|
||||||
|
|
||||||
|
|
||||||
class ListServers(ApiHandler):
|
|
||||||
def get(self):
|
|
||||||
user = self.authenticate_user()
|
|
||||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.api_token is None:
|
|
||||||
self.access_denied("unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
if user_obj["superuser"]:
|
|
||||||
servers = self.controller.servers.get_all_defined_servers()
|
|
||||||
servers = [str(i) for i in servers]
|
|
||||||
else:
|
|
||||||
servers = self.controller.servers.get_authorized_servers(
|
|
||||||
user_obj["user_id"]
|
|
||||||
)
|
|
||||||
page_servers = []
|
|
||||||
for server in servers:
|
|
||||||
if server not in page_servers:
|
|
||||||
page_servers.append(
|
|
||||||
DatabaseShortcuts.get_data_obj(server.server_object)
|
|
||||||
)
|
|
||||||
servers = page_servers
|
|
||||||
servers = [str(i) for i in servers]
|
|
||||||
|
|
||||||
self.return_response(
|
|
||||||
200,
|
|
||||||
{
|
|
||||||
"code": "COMPLETED",
|
|
||||||
"servers": servers,
|
|
||||||
},
|
|
||||||
)
|
|
@ -14,6 +14,7 @@ from app.classes.shared.translation import Translation
|
|||||||
from app.classes.shared.main_models import DatabaseShortcuts
|
from app.classes.shared.main_models import DatabaseShortcuts
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
auth_log = logging.getLogger("auth")
|
||||||
|
|
||||||
bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE)
|
bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE)
|
||||||
|
|
||||||
@ -231,9 +232,16 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||||||
user,
|
user,
|
||||||
)
|
)
|
||||||
logging.debug("Auth unsuccessful")
|
logging.debug("Auth unsuccessful")
|
||||||
|
auth_log.error(
|
||||||
|
f"Authentication attempted from {self.get_remote_ip()}. Invalid token"
|
||||||
|
)
|
||||||
self.access_denied(None, "the user provided an invalid token")
|
self.access_denied(None, "the user provided an invalid token")
|
||||||
return None
|
return None
|
||||||
except Exception as auth_exception:
|
except Exception as auth_exception:
|
||||||
|
auth_log.error(
|
||||||
|
f"Authentication attempted from {self.get_remote_ip()}."
|
||||||
|
f" Error: {auth_exception}"
|
||||||
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"An error occured while authenticating an API user:",
|
"An error occured while authenticating an API user:",
|
||||||
exc_info=auth_exception,
|
exc_info=auth_exception,
|
||||||
|
@ -481,6 +481,12 @@ class PanelHandler(BaseHandler):
|
|||||||
subpage = nh3.clean(self.get_argument("subpage", ""))
|
subpage = nh3.clean(self.get_argument("subpage", ""))
|
||||||
|
|
||||||
server_id = self.check_server_id()
|
server_id = self.check_server_id()
|
||||||
|
# load page the user was on last
|
||||||
|
server_subpage = self.controller.servers.server_subpage.get(server_id, "")
|
||||||
|
if subpage == "" and server_subpage != "":
|
||||||
|
subpage = self.controller.servers.server_subpage.get(server_id, "")
|
||||||
|
else:
|
||||||
|
self.controller.servers.server_subpage[server_id] = subpage
|
||||||
if server_id is None:
|
if server_id is None:
|
||||||
return
|
return
|
||||||
if not self.failed_server:
|
if not self.failed_server:
|
||||||
|
@ -6,6 +6,7 @@ from app.classes.models.users import HelperUsers
|
|||||||
from app.classes.web.base_handler import BaseHandler
|
from app.classes.web.base_handler import BaseHandler
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
auth_log = logging.getLogger("auth")
|
||||||
|
|
||||||
|
|
||||||
class PublicHandler(BaseHandler):
|
class PublicHandler(BaseHandler):
|
||||||
@ -96,6 +97,9 @@ class PublicHandler(BaseHandler):
|
|||||||
page_data["query"] = self.request.query
|
page_data["query"] = self.request.query
|
||||||
|
|
||||||
if page == "login":
|
if page == "login":
|
||||||
|
auth_log.info(
|
||||||
|
f"User attempting to authenticate from {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
next_page = "/login"
|
next_page = "/login"
|
||||||
if self.request.query:
|
if self.request.query:
|
||||||
next_page = "/login?" + self.request.query
|
next_page = "/login?" + self.request.query
|
||||||
@ -108,6 +112,12 @@ class PublicHandler(BaseHandler):
|
|||||||
user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
|
user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
|
||||||
user_data = HelperUsers.get_user_model(user_id)
|
user_data = HelperUsers.get_user_model(user_id)
|
||||||
except:
|
except:
|
||||||
|
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {entered_username}."
|
||||||
|
f" Authentication failed from remote IP {self.get_remote_ip()}"
|
||||||
|
" Users does not exist."
|
||||||
|
)
|
||||||
error_msg = "Incorrect username or password. Please try again."
|
error_msg = "Incorrect username or password. Please try again."
|
||||||
# self.clear_cookie("user")
|
# self.clear_cookie("user")
|
||||||
# self.clear_cookie("user_data")
|
# self.clear_cookie("user_data")
|
||||||
@ -120,6 +130,12 @@ class PublicHandler(BaseHandler):
|
|||||||
|
|
||||||
# if we don't have a user
|
# if we don't have a user
|
||||||
if not user_data:
|
if not user_data:
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {entered_username}. Authentication"
|
||||||
|
f" failed from remote IP {self.get_remote_ip()}"
|
||||||
|
" User does not exist."
|
||||||
|
)
|
||||||
|
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||||
error_msg = "Incorrect username or password. Please try again."
|
error_msg = "Incorrect username or password. Please try again."
|
||||||
# self.clear_cookie("user")
|
# self.clear_cookie("user")
|
||||||
# self.clear_cookie("user_data")
|
# self.clear_cookie("user_data")
|
||||||
@ -132,6 +148,12 @@ class PublicHandler(BaseHandler):
|
|||||||
|
|
||||||
# if they are disabled
|
# if they are disabled
|
||||||
if not user_data.enabled:
|
if not user_data.enabled:
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {entered_username}. "
|
||||||
|
f"Authentication failed from remote IP {self.get_remote_ip()}."
|
||||||
|
" User account disabled"
|
||||||
|
)
|
||||||
|
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||||
error_msg = (
|
error_msg = (
|
||||||
"User account disabled. Please contact "
|
"User account disabled. Please contact "
|
||||||
"your system administrator for more info."
|
"your system administrator for more info."
|
||||||
@ -159,7 +181,11 @@ class PublicHandler(BaseHandler):
|
|||||||
user_data.last_ip = self.get_remote_ip()
|
user_data.last_ip = self.get_remote_ip()
|
||||||
user_data.last_login = Helpers.get_time_as_string()
|
user_data.last_login = Helpers.get_time_as_string()
|
||||||
user_data.save()
|
user_data.save()
|
||||||
|
auth_log.info(
|
||||||
|
f"{entered_username} successfully"
|
||||||
|
" authenticated and logged"
|
||||||
|
f" into panel from remote IP {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
# log this login
|
# log this login
|
||||||
self.controller.management.add_to_audit_log(
|
self.controller.management.add_to_audit_log(
|
||||||
user_data.user_id, "Logged in", 0, self.get_remote_ip()
|
user_data.user_id, "Logged in", 0, self.get_remote_ip()
|
||||||
@ -172,6 +198,11 @@ class PublicHandler(BaseHandler):
|
|||||||
|
|
||||||
self.redirect(next_page)
|
self.redirect(next_page)
|
||||||
else:
|
else:
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {entered_username}."
|
||||||
|
f" Authentication failed from remote IP {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
|
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||||
# self.clear_cookie("user")
|
# self.clear_cookie("user")
|
||||||
# self.clear_cookie("user_data")
|
# self.clear_cookie("user_data")
|
||||||
self.clear_cookie("token")
|
self.clear_cookie("token")
|
||||||
|
@ -7,7 +7,7 @@ from app.classes.shared.helpers import Helpers
|
|||||||
from app.classes.web.base_api_handler import BaseApiHandler
|
from app.classes.web.base_api_handler import BaseApiHandler
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
auth_log = logging.getLogger("auth")
|
||||||
login_schema = {
|
login_schema = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -29,6 +29,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
|||||||
try:
|
try:
|
||||||
data = json.loads(self.request.body)
|
data = json.loads(self.request.body)
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
logger.error(
|
||||||
|
"Invalid JSON schema for API"
|
||||||
|
f" login attempt from {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
)
|
)
|
||||||
@ -36,6 +40,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
|||||||
try:
|
try:
|
||||||
validate(data, login_schema)
|
validate(data, login_schema)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
logger.error(
|
||||||
|
"Invalid JSON schema for API"
|
||||||
|
f" login attempt from {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400,
|
400,
|
||||||
{
|
{
|
||||||
@ -52,12 +60,23 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
|||||||
user_data = Users.get_or_none(Users.username == username)
|
user_data = Users.get_or_none(Users.username == username)
|
||||||
|
|
||||||
if user_data is None:
|
if user_data is None:
|
||||||
|
self.controller.log_attempt(self.get_remote_ip(), username)
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {username}."
|
||||||
|
" Authentication failed from remote IP"
|
||||||
|
f" {self.get_remote_ip()}. User not found"
|
||||||
|
)
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
401,
|
401,
|
||||||
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
|
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
|
||||||
)
|
)
|
||||||
|
|
||||||
if not user_data.enabled:
|
if not user_data.enabled:
|
||||||
|
auth_log.error(
|
||||||
|
f"User attempted to log into {username}."
|
||||||
|
" Authentication failed from remote"
|
||||||
|
f" IP {self.get_remote_ip()} account disabled"
|
||||||
|
)
|
||||||
self.finish_json(
|
self.finish_json(
|
||||||
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
|
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
|
||||||
)
|
)
|
||||||
@ -67,6 +86,11 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
|||||||
|
|
||||||
# Valid Login
|
# Valid Login
|
||||||
if login_result:
|
if login_result:
|
||||||
|
auth_log.info(
|
||||||
|
f"{username} successfully"
|
||||||
|
" authenticated and logged"
|
||||||
|
f" into panel from remote IP {self.get_remote_ip()}"
|
||||||
|
)
|
||||||
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
|
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
|
||||||
|
|
||||||
# record this login
|
# record this login
|
||||||
|
@ -110,7 +110,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
data = orjson.loads(self.request.body)
|
data = orjson.loads(self.request.body)
|
||||||
except orjson.decoder.JSONDecodeError as e:
|
except orjson.JSONDecodeError as e:
|
||||||
return self.finish_json(
|
return self.finish_json(
|
||||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||||
)
|
)
|
||||||
|
@ -22,18 +22,6 @@ from app.classes.web.default_handler import DefaultHandler
|
|||||||
from app.classes.web.routes.api.api_handlers import api_handlers
|
from app.classes.web.routes.api.api_handlers import api_handlers
|
||||||
from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers
|
from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers
|
||||||
from app.classes.web.server_handler import ServerHandler
|
from app.classes.web.server_handler import ServerHandler
|
||||||
from app.classes.web.api_handler import (
|
|
||||||
ServersStats,
|
|
||||||
NodeStats,
|
|
||||||
ServerBackup,
|
|
||||||
StartServer,
|
|
||||||
StopServer,
|
|
||||||
RestartServer,
|
|
||||||
CreateUser,
|
|
||||||
DeleteUser,
|
|
||||||
ListServers,
|
|
||||||
SendCommand,
|
|
||||||
)
|
|
||||||
from app.classes.web.websocket_handler import WebSocketHandler
|
from app.classes.web.websocket_handler import WebSocketHandler
|
||||||
from app.classes.web.static_handler import CustomStaticHandler
|
from app.classes.web.static_handler import CustomStaticHandler
|
||||||
from app.classes.web.upload_handler import UploadHandler
|
from app.classes.web.upload_handler import UploadHandler
|
||||||
@ -162,17 +150,6 @@ class Webserver:
|
|||||||
(r"/ws", WebSocketHandler, handler_args),
|
(r"/ws", WebSocketHandler, handler_args),
|
||||||
(r"/upload", UploadHandler, handler_args),
|
(r"/upload", UploadHandler, handler_args),
|
||||||
(r"/status", StatusHandler, handler_args),
|
(r"/status", StatusHandler, handler_args),
|
||||||
# API Routes V1
|
|
||||||
(r"/api/v1/stats/servers", ServersStats, handler_args),
|
|
||||||
(r"/api/v1/stats/node", NodeStats, handler_args),
|
|
||||||
(r"/api/v1/server/send_command", SendCommand, handler_args),
|
|
||||||
(r"/api/v1/server/backup", ServerBackup, handler_args),
|
|
||||||
(r"/api/v1/server/start", StartServer, handler_args),
|
|
||||||
(r"/api/v1/server/stop", StopServer, handler_args),
|
|
||||||
(r"/api/v1/server/restart", RestartServer, handler_args),
|
|
||||||
(r"/api/v1/list_servers", ListServers, handler_args),
|
|
||||||
(r"/api/v1/users/create_user", CreateUser, handler_args),
|
|
||||||
(r"/api/v1/users/delete_user", DeleteUser, handler_args),
|
|
||||||
# API Routes V2
|
# API Routes V2
|
||||||
*api_handlers(handler_args),
|
*api_handlers(handler_args),
|
||||||
# API Routes OpenMetrics
|
# API Routes OpenMetrics
|
||||||
|
@ -10,16 +10,17 @@
|
|||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
|
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"console": {
|
"console": {
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "commander",
|
"formatter": "commander",
|
||||||
"stream": "ext://sys.stdout"
|
"stream": "ext://sys.stdout"
|
||||||
},
|
},
|
||||||
|
|
||||||
"main_file_handler": {
|
"main_file_handler": {
|
||||||
"class": "logging.handlers.RotatingFileHandler",
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
"formatter": "commander",
|
"formatter": "commander",
|
||||||
@ -50,24 +51,45 @@
|
|||||||
"maxBytes": 10485760,
|
"maxBytes": 10485760,
|
||||||
"backupCount": 20,
|
"backupCount": 20,
|
||||||
"encoding": "utf8"
|
"encoding": "utf8"
|
||||||
|
},
|
||||||
|
"auth_file_handler": {
|
||||||
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
|
"formatter": "auth",
|
||||||
|
"filename": "logs/auth.log",
|
||||||
|
"maxBytes": 10485760,
|
||||||
|
"backupCount": 20,
|
||||||
|
"encoding": "utf8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"loggers": {
|
"loggers": {
|
||||||
"": {
|
"": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"handlers": ["main_file_handler", "session_file_handler"],
|
"handlers": [
|
||||||
|
"main_file_handler",
|
||||||
|
"session_file_handler"
|
||||||
|
],
|
||||||
"propagate": false
|
"propagate": false
|
||||||
},
|
},
|
||||||
"tornado.access": {
|
"tornado.access": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"handlers": ["tornado_access_file_handler"],
|
"handlers": [
|
||||||
|
"tornado_access_file_handler"
|
||||||
|
],
|
||||||
"propagate": false
|
"propagate": false
|
||||||
},
|
},
|
||||||
"apscheduler": {
|
"apscheduler": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"handlers": ["schedule_file_handler"],
|
"handlers": [
|
||||||
|
"schedule_file_handler"
|
||||||
|
],
|
||||||
|
"propagate": false
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"level": "INFO",
|
||||||
|
"handlers": [
|
||||||
|
"auth_file_handler"
|
||||||
|
],
|
||||||
"propagate": false
|
"propagate": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.6 KiB |
10
app/frontend/static/assets/images/crafty-logo-square.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 425 425">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect x="0" y="0" rx="105" ry="105" width="425" height="425" style="fill: #22283A"/>
|
||||||
|
<!-- green C -->
|
||||||
|
<path fill="hsl(160, 59%, 45%)" d="M 162.5 182.5 l 38 -15 q 0 -40 -40 -40 l -95 0 q -40 0 -40 40 l 0 90 q 0 40 40 40 l 95 0 q 40 0 40 -40 l -38 -20 l 0 28 l -100 0 l 0 -108 l 100 0 z"/>
|
||||||
|
<!-- blue C -->
|
||||||
|
<path fill="hsl(192, 99%, 45%)" d="M 262.5 182.5 l -38 -15 q 0 -40 40 -40 l 95 0 q 40 0 40 40 l 0 90 q 0 40 -40 40 l -95 0 q -40 0 -40 -40 l 38 -20 l 0 28 l 100 0 l 0 -108 l -100 0 z"/>
|
||||||
|
<!-- line thing in middle of Cs -->
|
||||||
|
<path fill="hsl(266, 71%, 57%)" d="M 142.5 191.5 q -10 0 -10 10 l 0 19 q 0 10 10 10 l 140 0 q 10 0 10 -10 l 0 -19 q 0 -10 -10 -10 z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 750 B |
@ -82,6 +82,9 @@
|
|||||||
<span class="mdi mdi-chevron-double-left"></span>
|
<span class="mdi mdi-chevron-double-left"></span>
|
||||||
<span class="mdi mdi-chevron-double-right"></span>
|
<span class="mdi mdi-chevron-double-right"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<span class="badge-pill badge-outline-primary" id="server-name-nav" style="display: none;"></span>
|
||||||
|
|
||||||
|
|
||||||
{% include notify.html %}
|
{% include notify.html %}
|
||||||
|
|
||||||
|
@ -248,12 +248,38 @@
|
|||||||
$("#player-body").html(text);
|
$("#player-body").html(text);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
const token = getCookie("_xsrf")
|
||||||
$(window).ready(function () {
|
$(window).ready(function () {
|
||||||
console.log("ready!");
|
console.log("ready!");
|
||||||
|
|
||||||
//if (webSocket) {
|
//if (webSocket) {
|
||||||
webSocket.on('update_server_details', update_server_details);
|
webSocket.on('update_server_details', update_server_details);
|
||||||
|
add_server_name();
|
||||||
//}
|
//}
|
||||||
});
|
});
|
||||||
|
async function add_server_name(){
|
||||||
|
let res = await fetch(`/api/v2/servers/${serverId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let responseData = await res.json();
|
||||||
|
if (responseData.status === "ok") {
|
||||||
|
console.log(responseData)
|
||||||
|
$("#server-name-nav").html(`${responseData.data['server_name']}`)
|
||||||
|
$("#server-name-nav").show();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bootbox.alert({
|
||||||
|
title: responseData.error,
|
||||||
|
message: responseData.error_data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@ -69,7 +69,7 @@
|
|||||||
<td>Banned on {{ player['banned_on'] }}</td>
|
<td>Banned on {{ player['banned_on'] }}</td>
|
||||||
<td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td>
|
<td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td>
|
||||||
<td class="buttons">
|
<td class="buttons">
|
||||||
<button onclick="send_command_to_server(`pardon {{ player['name'] }} `)" type="button" class="btn btn-danger">Unban</button>
|
<button onclick="send_command_to_server(`pardon {{ player['name'] }}`)" type="button" class="btn btn-danger">Unban</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% end %}
|
{% end %}
|
||||||
|
@ -43,8 +43,10 @@
|
|||||||
<form class="forms-sample" method="post" id="webhook_form"
|
<form class="forms-sample" method="post" id="webhook_form"
|
||||||
action="/panel/edit_webhook?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['webhook']['id'] }}">
|
action="/panel/edit_webhook?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['webhook']['id'] }}">
|
||||||
{% end %}
|
{% end %}
|
||||||
<select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type">
|
<select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type">
|
||||||
|
{% if data['new_webhook'] == False %}
|
||||||
<option value="{{data['webhook']['webhook_type']}}">{{data['webhook']['webhook_type']}}</option>
|
<option value="{{data['webhook']['webhook_type']}}">{{data['webhook']['webhook_type']}}</option>
|
||||||
|
{% end %}
|
||||||
{% for type in data['providers'] %}
|
{% for type in data['providers'] %}
|
||||||
{% if type != data['webhook']['webhook_type'] %}
|
{% if type != data['webhook']['webhook_type'] %}
|
||||||
<option value="{{type}}">{{type}}</option>
|
<option value="{{type}}">{{type}}</option>
|
||||||
|
8
main.py
@ -250,6 +250,14 @@ def setup_logging(debug=True):
|
|||||||
determines whether or not the logging level is "debug" or verbose.
|
determines whether or not the logging level is "debug" or verbose.
|
||||||
"""
|
"""
|
||||||
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
|
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
|
||||||
|
if not helper.check_file_exists(
|
||||||
|
os.path.join(os.path.curdir, "logs", "auth_tracker.log")
|
||||||
|
):
|
||||||
|
open(
|
||||||
|
os.path.join(os.path.curdir, "logs", "auth_tracker.log"),
|
||||||
|
"a",
|
||||||
|
encoding="utf-8",
|
||||||
|
).close()
|
||||||
|
|
||||||
if os.path.exists(logging_config_file):
|
if os.path.exists(logging_config_file):
|
||||||
# open our logging config file
|
# open our logging config file
|
||||||
|