Merge branch 'dev' into feature/jinja2-for-webhooks

This commit is contained in:
Zedifus 2024-05-09 21:26:52 +01:00
commit 95bb4bddfe
58 changed files with 590 additions and 361 deletions

View File

@ -5,7 +5,7 @@ yamllint:
stage: lint stage: lint
image: registry.gitlab.com/pipeline-components/yamllint:latest image: registry.gitlab.com/pipeline-components/yamllint:latest
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$CODE_QUALITY_DISABLED" - if: "$CODE_QUALITY_DISABLED"
when: never when: never
@ -18,7 +18,7 @@ jsonlint:
stage: lint stage: lint
image: registry.gitlab.com/pipeline-components/jsonlint:latest image: registry.gitlab.com/pipeline-components/jsonlint:latest
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$CODE_QUALITY_DISABLED" - if: "$CODE_QUALITY_DISABLED"
when: never when: never
@ -33,7 +33,7 @@ black:
stage: lint stage: lint
image: registry.gitlab.com/pipeline-components/black:latest image: registry.gitlab.com/pipeline-components/black:latest
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$CODE_QUALITY_DISABLED" - if: "$CODE_QUALITY_DISABLED"
when: never when: never
@ -46,7 +46,7 @@ pylint:
stage: lint stage: lint
image: registry.gitlab.com/pipeline-components/pylint:latest image: registry.gitlab.com/pipeline-components/pylint:latest
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$CODE_QUALITY_DISABLED" - if: "$CODE_QUALITY_DISABLED"
when: never when: never
@ -69,7 +69,7 @@ sonarcloud-check:
name: sonarsource/sonar-scanner-cli:latest name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""] entrypoint: [""]
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$SONAR_TOKEN == null" - if: "$SONAR_TOKEN == null"
when: never when: never
@ -91,7 +91,7 @@ lang-check:
stage: lint stage: lint
image: alpine:latest image: alpine:latest
tags: tags:
- docker - saas-linux-medium-amd64
rules: rules:
- if: "$CODE_QUALITY_DISABLED" - if: "$CODE_QUALITY_DISABLED"
when: never when: never

View File

@ -1,13 +1,17 @@
# Changelog # Changelog
## --- [4.3.3] - 2024/TBD ## --- [4.3.3] - 2024/TBD
### Refactor
- Refactor API keys "super user" to "full access" ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/731))
### New features ### New features
TBD TBD
### Bug fixes ### Bug fixes
TBD - Reset query arguments on login if `?next` is not available ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/750))
- Fix child schedule failing to load after del parent ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/753))
### Tweaks ### Tweaks
TBD - Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743))
- Set audit logging to logfile instead of DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/751))
### Lang ### Lang
TBD - Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749))
<br><br> <br><br>
## --- [4.3.2] - 2024/04/07 ## --- [4.3.2] - 2024/04/07

View File

@ -95,9 +95,6 @@ class ManagementController:
# ********************************************************************************** # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
# ********************************************************************************** # **********************************************************************************
@staticmethod
def get_activity_log():
return HelpersManagement.get_activity_log()
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
return self.management_helper.add_to_audit_log( return self.management_helper.add_to_audit_log(

View File

@ -17,6 +17,10 @@ class ServerPermsController:
def get_server_user_list(server_id): def get_server_user_list(server_id):
return PermissionsServers.get_server_user_list(server_id) return PermissionsServers.get_server_user_list(server_id)
@staticmethod
def get_permissions(permissions_mask):
return PermissionsServers.get_permissions(permissions_mask)
@staticmethod @staticmethod
def list_defined_permissions(): def list_defined_permissions():
permissions_list = PermissionsServers.get_permissions_list() permissions_list = PermissionsServers.get_permissions_list()
@ -61,6 +65,22 @@ class ServerPermsController:
def get_permissions_mask(role_id, server_id): def get_permissions_mask(role_id, server_id):
return PermissionsServers.get_permissions_mask(role_id, server_id) return PermissionsServers.get_permissions_mask(role_id, server_id)
@staticmethod
def get_lowest_api_perm_mask(user_server_permissions_mask, api_key_permssions_mask):
mask = ""
# If this isn't an API key we'll know the request came from basic
# authentication and ignore the API key permissions mask.
if not api_key_permssions_mask:
return user_server_permissions_mask
for _index, (user_perm, api_perm) in enumerate(
zip(user_server_permissions_mask, api_key_permssions_mask)
):
if user_perm == "1" and api_perm == "1":
mask += "1"
else:
mask += "0"
return mask
@staticmethod @staticmethod
def set_permission( def set_permission(
permission_mask, permission_tested: EnumPermissionsServer, value permission_mask, permission_tested: EnumPermissionsServer, value
@ -82,6 +102,11 @@ class ServerPermsController:
def get_api_key_permissions_list(key: ApiKeys, server_id: str): def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return PermissionsServers.get_api_key_permissions_list(key, server_id) return PermissionsServers.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_user_permissions_mask(user_id: str, server_id: str):
user = HelperUsers.get_user_model(user_id)
return PermissionsServers.get_user_permissions_mask(user, server_id)
@staticmethod @staticmethod
def get_authorized_servers_stats_from_roles(user_id): def get_authorized_servers_stats_from_roles(user_id):
user_roles = HelperUsers.get_user_roles_id(user_id) user_roles = HelperUsers.get_user_roles_id(user_id)

View File

@ -0,0 +1,53 @@
import logging
import logging.config
import json
from datetime import datetime
class JsonEncoderStrFallback(json.JSONEncoder):
def default(self, o):
try:
return super().default(o)
except TypeError as exc:
if "not JSON serializable" in str(exc):
return str(o)
raise
class JsonEncoderDatetime(JsonEncoderStrFallback):
def default(self, o):
if isinstance(o, datetime):
return o.strftime("%Y-%m-%dT%H:%M:%S%z")
return super().default(o)
class JsonFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
"""
Override formatTime to customize the time format.
"""
timestamp = datetime.fromtimestamp(record.created)
if datefmt:
# Use the specified date format
return timestamp.strftime(datefmt)
# Default date format: YYYY-MM-DD HH:MM:SS,mmm
secs = int(record.msecs)
return f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')},{secs:03d}"
def format(self, record):
log_data = {
"level": record.levelname,
"time": self.formatTime(record),
"log_msg": record.getMessage(),
}
# Filter out standard log record attributes and include only custom ones
custom_attrs = ["user_name", "user_id", "server_id", "source_ip"]
extra_attrs = {
key: value for key, value in record.__dict__.items() if key in custom_attrs
}
# Merge extra attributes with log data
log_data.update(extra_attrs)
return json.dumps(log_data)

View File

@ -187,7 +187,7 @@ class PermissionsCrafty:
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys): def get_api_key_permissions_list(key: ApiKeys):
user = HelperUsers.get_user(key.user_id) user = HelperUsers.get_user(key.user_id)
if user["superuser"] and key.superuser: if user["superuser"] and key.full_access:
return PermissionsCrafty.get_permissions_list() return PermissionsCrafty.get_permissions_list()
if user["superuser"]: if user["superuser"]:
# User is superuser but API key isn't # User is superuser but API key isn't

View File

@ -16,28 +16,10 @@ from app.classes.models.base_model import BaseModel
from app.classes.models.users import HelperUsers from app.classes.models.users import HelperUsers
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.models.server_permissions import PermissionsServers from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.shared.websocket_manager import WebSocketManager from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
auth_logger = logging.getLogger("audit_log")
# **********************************************************************************
# Audit_Log Class
# **********************************************************************************
class AuditLog(BaseModel):
audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="")
user_id = IntegerField(default=0, index=True)
source_ip = CharField(default="127.0.0.1")
server_id = ForeignKeyField(
Servers, backref="audit_server", null=True
) # When auditing global events, use server ID null
log_msg = TextField(default="")
class Meta:
table_name = "audit_log"
# ********************************************************************************** # **********************************************************************************
@ -149,10 +131,6 @@ class HelpersManagement:
# ********************************************************************************** # **********************************************************************************
# Audit_Log Methods # Audit_Log Methods
# ********************************************************************************** # **********************************************************************************
@staticmethod
def get_activity_log():
query = AuditLog.select()
return DatabaseShortcuts.return_db_rows(query)
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ") logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
@ -166,50 +144,28 @@ class HelpersManagement:
WebSocketManager().broadcast_user(user, "notification", audit_msg) WebSocketManager().broadcast_user(user, "notification", audit_msg)
except Exception as e: except Exception as e:
logger.error(f"Error broadcasting to user {user} - {e}") logger.error(f"Error broadcasting to user {user} - {e}")
auth_logger.info(
AuditLog.insert( str(log_msg),
{ extra={
AuditLog.user_name: user_data["username"], "user_name": user_data["username"],
AuditLog.user_id: user_id, "user_id": user_id,
AuditLog.server_id: server_id, "server_id": server_id,
AuditLog.log_msg: audit_msg, "source_ip": source_ip,
AuditLog.source_ip: source_ip, },
} )
).execute()
# deletes records when there's more than 300
ordered = AuditLog.select().order_by(+AuditLog.created)
for item in ordered:
if not self.helper.get_setting("max_audit_entries"):
max_entries = 300
else:
max_entries = self.helper.get_setting("max_audit_entries")
if AuditLog.select().count() > max_entries:
AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else:
return
def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip): def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
AuditLog.insert( if isinstance(server_id, Servers) and server_id is not None:
{ server_id = server_id.server_id
AuditLog.user_name: user_name, auth_logger.info(
AuditLog.user_id: user_id, str(log_msg),
AuditLog.server_id: server_id, extra={
AuditLog.log_msg: log_msg, "user_name": user_name,
AuditLog.source_ip: source_ip, "user_id": user_id,
} "server_id": server_id,
).execute() "source_ip": source_ip,
# deletes records when there's more than 300 },
ordered = AuditLog.select().order_by(+AuditLog.created) )
for item in ordered:
# configurable through app/config/config.json
if not self.helper.get_setting("max_audit_entries"):
max_entries = 300
else:
max_entries = self.helper.get_setting("max_audit_entries")
if AuditLog.select().count() > max_entries:
AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else:
return
@staticmethod @staticmethod
def create_crafty_row(): def create_crafty_row():

View File

@ -264,7 +264,7 @@ class PermissionsServers:
@staticmethod @staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str): def get_api_key_permissions_list(key: ApiKeys, server_id: str):
user = HelperUsers.get_user(key.user_id) user = HelperUsers.get_user(key.user_id)
if user["superuser"] and key.superuser: if user["superuser"] and key.full_access:
return PermissionsServers.get_permissions_list() return PermissionsServers.get_permissions_list()
roles_list = HelperUsers.get_user_roles_id(user["user_id"]) roles_list = HelperUsers.get_user_roles_id(user["user_id"])
role_server = ( role_server = (

View File

@ -71,7 +71,7 @@ class ApiKeys(BaseModel):
user_id = ForeignKeyField(Users, backref="api_token", index=True) user_id = ForeignKeyField(Users, backref="api_token", index=True)
server_permissions = CharField(default="00000000") server_permissions = CharField(default="00000000")
crafty_permissions = CharField(default="000") crafty_permissions = CharField(default="000")
superuser = BooleanField(default=False) full_access = BooleanField(default=False)
class Meta: class Meta:
table_name = "api_keys" table_name = "api_keys"
@ -408,7 +408,7 @@ class HelperUsers:
def add_user_api_key( def add_user_api_key(
name: str, name: str,
user_id: str, user_id: str,
superuser: bool = False, full_access: bool = False,
server_permissions_mask: t.Optional[str] = None, server_permissions_mask: t.Optional[str] = None,
crafty_permissions_mask: t.Optional[str] = None, crafty_permissions_mask: t.Optional[str] = None,
): ):
@ -426,7 +426,7 @@ class HelperUsers:
if crafty_permissions_mask is not None if crafty_permissions_mask is not None
else {} else {}
), ),
ApiKeys.superuser: superuser, ApiKeys.full_access: full_access,
} }
).execute() ).execute()

View File

@ -182,6 +182,7 @@ class BaseHandler(tornado.web.RequestHandler):
t.List[str], t.List[str],
bool, bool,
t.Dict[str, t.Any], t.Dict[str, t.Any],
str,
] ]
]: ]:
try: try:
@ -190,9 +191,10 @@ class BaseHandler(tornado.web.RequestHandler):
) )
superuser = user["superuser"] superuser = user["superuser"]
server_permissions_api_mask = ""
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
server_permissions_api_mask = api_key.server_permissions
exec_user_role = set() exec_user_role = set()
if superuser: if superuser:
authorized_servers = self.controller.servers.get_all_defined_servers() authorized_servers = self.controller.servers.get_all_defined_servers()
@ -214,6 +216,7 @@ class BaseHandler(tornado.web.RequestHandler):
user["user_id"] user["user_id"]
) )
) )
logger.debug(user["roles"]) logger.debug(user["roles"])
for r in user["roles"]: for r in user["roles"]:
role = self.controller.roles.get_role(r) role = self.controller.roles.get_role(r)
@ -234,6 +237,7 @@ class BaseHandler(tornado.web.RequestHandler):
exec_user_role, exec_user_role,
superuser, superuser,
user, user,
server_permissions_api_mask,
) )
logging.debug("Auth unsuccessful") logging.debug("Auth unsuccessful")
auth_log.error( auth_log.error(

View File

@ -168,7 +168,7 @@ class PanelHandler(BaseHandler):
# Commented out because there is no server access control for API keys, # Commented out because there is no server access control for API keys,
# they just inherit from the host user # they just inherit from the host user
# if api_key is not None: # if api_key is not None:
# superuser = superuser and api_key.superuser # superuser = superuser and api_key.full_access
if server_id is None: if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID") self.redirect("/panel/error?error=Invalid Server ID")
@ -242,7 +242,7 @@ class PanelHandler(BaseHandler):
api_key, _token_data, exec_user = self.current_user api_key, _token_data, exec_user = self.current_user
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
if superuser: # TODO: Figure out a better solution if superuser: # TODO: Figure out a better solution
defined_servers = self.controller.servers.list_defined_servers() defined_servers = self.controller.servers.list_defined_servers()
@ -351,7 +351,7 @@ class PanelHandler(BaseHandler):
"created": api_key.created, "created": api_key.created,
"server_permissions": api_key.server_permissions, "server_permissions": api_key.server_permissions,
"crafty_permissions": api_key.crafty_permissions, "crafty_permissions": api_key.crafty_permissions,
"superuser": api_key.superuser, "full_access": api_key.full_access,
} }
if api_key is not None if api_key is not None
else None else None
@ -1239,9 +1239,11 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["interval_type"] = schedule.interval_type page_data["schedule"]["interval_type"] = schedule.interval_type
if schedule.interval_type == "reaction": if schedule.interval_type == "reaction":
difficulty = "reaction" difficulty = "reaction"
page_data["parent"] = self.controller.management.get_scheduled_task( page_data["parent"] = None
schedule.parent if schedule.parent:
) page_data["parent"] = self.controller.management.get_scheduled_task(
schedule.parent
)
elif schedule.cron_string == "": elif schedule.cron_string == "":
difficulty = "basic" difficulty = "basic"
page_data["parent"] = None page_data["parent"] = None
@ -1358,6 +1360,9 @@ class PanelHandler(BaseHandler):
page_data["crafty_permissions_all"] = ( page_data["crafty_permissions_all"] = (
self.controller.crafty_perms.list_defined_crafty_permissions() self.controller.crafty_perms.list_defined_crafty_permissions()
) )
page_data["user_crafty_permissions"] = (
self.controller.crafty_perms.get_crafty_permissions_list(user_id)
)
if user_id is None: if user_id is None:
self.redirect("/panel/error?error=Invalid User ID") self.redirect("/panel/error?error=Invalid User ID")
@ -1505,8 +1510,6 @@ class PanelHandler(BaseHandler):
template = "panel/panel_edit_role.html" template = "panel/panel_edit_role.html"
elif page == "activity_logs": elif page == "activity_logs":
page_data["audit_logs"] = self.controller.management.get_activity_log()
template = "panel/activity_logs.html" template = "panel/activity_logs.html"
elif page == "download_file": elif page == "download_file":

View File

@ -48,7 +48,10 @@ class PublicHandler(BaseHandler):
} }
if self.request.query: if self.request.query:
page_data["query"] = self.request.query_arguments.get("next")[0].decode() request_query = self.request.query_arguments.get("next")
if not request_query:
self.redirect("/login")
page_data["query"] = request_query[0].decode()
# sensible defaults # sensible defaults
template = "public/404.html" template = "public/404.html"

View File

@ -1,3 +1,5 @@
import os
import json
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
@ -22,9 +24,17 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
raise NotImplementedError raise NotImplementedError
if log_type == "audit": if log_type == "audit":
with open(
os.path.join(self.controller.project_root, "logs", "audit.log"),
"r",
encoding="utf-8",
) as f:
log_lines = [json.loads(line) for line in f]
rev_log_lines = log_lines[::-1]
return self.finish_json( return self.finish_json(
200, 200,
{"status": "ok", "data": self.controller.management.get_activity_log()}, {"status": "ok", "data": rev_log_lines},
) )
if log_type == "session": if log_type == "session":

View File

@ -18,13 +18,14 @@ class ApiServersServerActionHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.COMMANDS
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error # if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -26,12 +26,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.BACKUP self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_backup_config(server_id)) self.finish_json(200, self.controller.management.get_backup_config(server_id))
@ -41,12 +43,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
backup_conf = self.controller.management.get_backup_config(server_id) backup_conf = self.controller.management.get_backup_config(server_id)
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.BACKUP self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -89,12 +93,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.BACKUP self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -42,12 +42,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.BACKUP self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_backup_config(server_id)) self.finish_json(200, self.controller.management.get_backup_config(server_id))
@ -82,13 +84,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.BACKUP
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -80,16 +80,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
),
auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if ( if (
EnumPermissionsServer.FILES EnumPermissionsServer.FILES not in server_permissions
not in self.controller.server_perms.get_user_id_permissions_list( and EnumPermissionsServer.BACKUP not in server_permissions
auth_data[4]["user_id"], server_id
)
and EnumPermissionsServer.BACKUP
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
): ):
# if the user doesn't have Files or Backup permission, return an error # if the user doesn't have Files or Backup permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -197,13 +197,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -254,13 +255,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -307,13 +309,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -373,13 +376,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -438,13 +442,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -504,13 +509,14 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error # if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:

View File

@ -102,13 +102,14 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error # if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -154,13 +155,14 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error # if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -30,13 +30,14 @@ class ApiServersServerLogsHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.LOGS
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.LOGS not in server_permissions:
# if the user doesn't have Logs permission, return an error # if the user doesn't have Logs permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -16,13 +16,14 @@ class ApiServersServerStdinHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.COMMANDS
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error # if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -78,13 +78,14 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
data["server_id"] = server_id data["server_id"] = server_id

View File

@ -54,12 +54,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.SCHEDULE self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_scheduled_task(task_id)) self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
@ -68,12 +70,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.SCHEDULE self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -120,13 +124,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.SCHEDULE
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})

View File

@ -38,12 +38,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.CONFIG self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json( self.finish_json(
@ -81,13 +83,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
data["server_id"] = server_id data["server_id"] = server_id

View File

@ -39,12 +39,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.CONFIG self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if ( if (
@ -66,12 +68,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return
if ( mask = self.controller.server_perms.get_lowest_api_perm_mask(
EnumPermissionsServer.CONFIG self.controller.server_perms.get_user_permissions_mask(
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -117,13 +121,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@ -159,13 +164,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]: if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error # if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
mask = self.controller.server_perms.get_lowest_api_perm_mask(
if ( self.controller.server_perms.get_user_permissions_mask(
EnumPermissionsServer.CONFIG
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id auth_data[4]["user_id"], server_id
) ),
): auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error # if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
webhook = self.controller.management.get_webhook_by_id(webhook_id) webhook = self.controller.management.get_webhook_by_id(webhook_id)

View File

@ -75,7 +75,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
"name": key.name, "name": key.name,
"server_permissions": key.server_permissions, "server_permissions": key.server_permissions,
"crafty_permissions": key.crafty_permissions, "crafty_permissions": key.crafty_permissions,
"superuser": key.superuser, "full_access": key.full_access,
} }
) )
self.finish_json( self.finish_json(
@ -99,7 +99,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
"type": "string", "type": "string",
"pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty "pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty
}, },
"superuser": {"type": "boolean"}, "full_access": {"type": "boolean"},
}, },
"additionalProperties": False, "additionalProperties": False,
"minProperties": 1, "minProperties": 1,
@ -163,7 +163,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
key_id = self.controller.users.add_user_api_key( key_id = self.controller.users.add_user_api_key(
data["name"], data["name"],
user_id, user_id,
data["superuser"], data["full_access"],
data["server_permissions_mask"], data["server_permissions_mask"],
data["crafty_permissions_mask"], data["crafty_permissions_mask"],
) )

View File

@ -30,7 +30,7 @@ class ServerHandler(BaseHandler):
) = self.current_user ) = self.current_user
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
if superuser: if superuser:
defined_servers = self.controller.servers.list_defined_servers() defined_servers = self.controller.servers.list_defined_servers()
@ -124,7 +124,7 @@ class ServerHandler(BaseHandler):
"created": api_key.created, "created": api_key.created,
"server_permissions": api_key.server_permissions, "server_permissions": api_key.server_permissions,
"crafty_permissions": api_key.crafty_permissions, "crafty_permissions": api_key.crafty_permissions,
"superuser": api_key.superuser, "full_access": api_key.full_access,
} }
if api_key is not None if api_key is not None
else None else None

View File

@ -42,7 +42,7 @@ class UploadHandler(BaseHandler):
if self.upload_type == "server_import": if self.upload_type == "server_import":
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
user_id = exec_user["user_id"] user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB") stream_size_value = self.helper.get_setting("stream_size_GB")
@ -133,7 +133,7 @@ class UploadHandler(BaseHandler):
elif self.upload_type == "background": elif self.upload_type == "background":
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
user_id = exec_user["user_id"] user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB") stream_size_value = self.helper.get_setting("stream_size_GB")
@ -212,7 +212,7 @@ class UploadHandler(BaseHandler):
server_id = self.get_argument("server_id", None) server_id = self.get_argument("server_id", None)
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
superuser = superuser and api_key.superuser superuser = superuser and api_key.full_access
user_id = exec_user["user_id"] user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB") stream_size_value = self.helper.get_setting("stream_size_GB")

View File

@ -14,6 +14,9 @@
"auth": { "auth": {
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s" "format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
}, },
"audit": {
"()": "app.classes.logging.log_formatter.JsonFormatter"
},
"cmd_queue": { "cmd_queue": {
"format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s" "format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s"
} }
@ -70,6 +73,14 @@
"maxBytes": 10485760, "maxBytes": 10485760,
"backupCount": 20, "backupCount": 20,
"encoding": "utf8" "encoding": "utf8"
},
"audit_log_handler": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "audit",
"filename": "logs/audit.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
} }
}, },
"loggers": { "loggers": {
@ -108,6 +119,12 @@
"cmd_queue_file_handler" "cmd_queue_file_handler"
], ],
"propagate": false "propagate": false
},
"audit_log": {
"level": "INFO",
"handlers": [
"audit_log_handler"
]
} }
} }
} }

View File

@ -36,25 +36,21 @@
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%"> <table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
<thead> <thead>
<tr class="rounded"> <tr class="rounded">
<td>Username</td> <th>Time</th>
<td>Time</td> <th>Username</th>
<td>Action</td> <th>Action</th>
<td>Server ID</td> <th>Server ID</th>
<td>IP</td> <th>IP</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in data['audit_logs'] %}
<tr> <tr>
<td>{{ row['user_name'] }}</td> <td colspan="5" id="image-div" class="text-center"> <!-- Center image within table -->
<td> <img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
{{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }} alt="Crafty Logo, Crafty is loading" width="20%"><br><br>{{ translate('datatables',
'loadingRecords', data['lang'])}}
</td> </td>
<td>{{ row['log_msg'] }}</td>
<td>{{ row['server_id'] }}</td>
<td>{{ row['source_ip'] }}</td>
</tr> </tr>
{% end %}
</tbody> </tbody>
</table> </table>
@ -79,17 +75,6 @@
{% end %} {% end %}
{% block js %} {% block js %}
<script>
$(document).ready(function () {
console.log('ready for JS!')
$('#audit_table').DataTable({
'order': [1, 'desc']
}
);
});
</script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
@ -112,6 +97,74 @@
$('.too_small').popover("hide"); $('.too_small').popover("hide");
} // New width } // New width
}); });
$(document).ready(function () {
console.log('ready for JS!')
// Initialize DataTables
// Load initial data
getActivity();
});
function updateActivity(data) {
let tbody = $('#audit_table tbody');
tbody.empty(); // Clear existing rows
$.each(data, function (index, value) {
let row = $('<tr>');
row.append(`<td>${value.time}</td>`);
if (value.user_name != "system" && value.user_id != "-1") {
row.append(`<td><a href="/panel/edit_user?id=${value.user_id}">${value.user_name}</a></td>`);
} else {
row.append(`<td>${value.user_name}</td>`);
}
row.append(`<td>${value.log_msg}</td>`);
row.append(`<td>${value.server_id}</td>`);
row.append(`<td>${value.source_ip}</td>`);
tbody.append(row);
});
$('#audit_table').DataTable({
'order': [[0, 'desc']], // Sort by the first column in descending order
filter: true,
"searching": true,
})
}
async function getActivity() {
var token = getCookie("_xsrf");
let res = await fetch(`/api/v2/crafty/logs/audit`, {
method: 'GET',
headers: {
'X-XSRFToken': token
},
});
let responseData = await res.json();
console.log(responseData);
if (responseData.status === "ok") {
updateActivity(responseData.data);
console.log("activity update")
} else {
bootbox.alert(responseData.error)
}
}
function rotateImage(degree) {
$('#logo-animate').animate({ transform: degree }, {
step: function (now, fx) {
$(this).css({
'-webkit-transform': 'rotate(' + now + 'deg)',
'-moz-transform': 'rotate(' + now + 'deg)',
'transform': 'rotate(' + now + 'deg)'
});
}
});
setTimeout(function () {
rotateImage(360);
}, 2000);
}
$(document).ready(function () {
setTimeout(function () {
rotateImage(360);
}, 2000);
});
</script> </script>
{% end %} {% end %}

View File

@ -58,7 +58,7 @@
<!--<th>ID</th>--> <!--<th>ID</th>-->
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th> <th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th> <th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th> <th>{{ translate('apiKeys', 'fullAccess', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th> <th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th> <th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
</tr> </tr>
@ -70,7 +70,7 @@
<td>{{ apikey.name }}</td> <td>{{ apikey.name }}</td>
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td> <td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
<td> <td>
{% if apikey.superuser %} {% if apikey.full_access %}
<span class="text-success"> <span class="text-success">
<i class="fas fa-check-square"></i> {{ <i class="fas fa-check-square"></i> {{
translate('apiKeys', 'yes', data['lang']) }} translate('apiKeys', 'yes', data['lang']) }}
@ -148,9 +148,15 @@
}}</label> }}</label>
</td> </td>
<td> <td>
{% if permission in data['user_crafty_permissions'] %}
<input type="checkbox" class="crafty_perm" <input type="checkbox" class="crafty_perm"
id="permission_{{ permission.name }}" id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1"> name="permission_{{ permission.name }}" value="1">
{% else %}
<input type="checkbox" class="crafty_perm"
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1" disabled>
{% end %}
</td> </td>
</tr> </tr>
{% end %} {% end %}
@ -158,8 +164,8 @@
</tbody> </tbody>
</table> </table>
<label for="superuser">Superuser</label> <label for="full_access">{{translate('apiKeys', 'fullAccess', data['lang'])}}</label>
<input type="checkbox" class="" id="superuser" name="superuser" value="1"> <input type="checkbox" class="" id="full_access" name="full_access" value="1">
<br /> <br />
@ -240,7 +246,7 @@
"name": formDataObject.name, "name": formDataObject.name,
"server_permissions_mask": server_permissions, "server_permissions_mask": server_permissions,
"crafty_permissions_mask": crafty_permissions, "crafty_permissions_mask": crafty_permissions,
"superuser": $("#superuser").prop('checked'), "full_access": $("#full_access").prop('checked'),
}); });
console.log(formDataJsonString); console.log(formDataJsonString);

View File

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ data.get('lang_page', 'en') }}" class="{{data['user_data'].get('theme', 'default')}}"
data-username="{{data['user_data'].get('username', None)}}">
<head> <head>
<!-- Required meta tags --> <!-- Required meta tags -->
@ -60,6 +61,11 @@
<b>{{ translate('error', 'hereIsTheError', data['lang']) }}: {{data['error']}}</b><br /><br /> <b>{{ translate('error', 'hereIsTheError', data['lang']) }}: {{data['error']}}</b><br /><br />
That's all the help I can give you - Godspeed That's all the help I can give you - Godspeed
<br /><br /> <br /><br />
<a class="d-inline font-weight-medium" href="/panel/dashboard"><button class="btn btn-info">{{
translate('error', 'return',
data['lang'])}}</button></a>
<br>
<br>
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('error', <a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('error',
'contact', data['lang']) }}</a> 'contact', data['lang']) }}</a>
</p> </p>

View File

@ -171,7 +171,6 @@
//Create an object from the form data entries //Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries()); let formDataObject = Object.fromEntries(formData.entries());
console.log(formDataObject)
let res = await fetch(`/login`, { let res = await fetch(`/login`, {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@ -6,7 +6,6 @@ import logging
from app.classes.shared.console import Console from app.classes.shared.console import Console
from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.shared.migration import Migrator, MigrateHistory
from app.classes.models.management import ( from app.classes.models.management import (
AuditLog,
Webhooks, Webhooks,
Schedules, Schedules,
Backups, Backups,
@ -61,17 +60,6 @@ def migrate(migrator: Migrator, database, **kwargs):
peewee.CharField(primary_key=True, default=str(uuid.uuid4())), peewee.CharField(primary_key=True, default=str(uuid.uuid4())),
) )
# Changes on Audit Log Table
migrator.alter_column_type(
AuditLog,
"server_id",
peewee.ForeignKeyField(
Servers,
backref="audit_server",
null=True,
field=peewee.CharField(primary_key=True, default=str(uuid.uuid4())),
),
)
# Changes on Webhook Table # Changes on Webhook Table
migrator.alter_column_type( migrator.alter_column_type(
Webhooks, Webhooks,
@ -109,13 +97,6 @@ def rollback(migrator: Migrator, database, **kwargs):
peewee.AutoField(), peewee.AutoField(),
) )
# Changes on Audit Log Table
migrator.alter_column_type(
AuditLog,
"server_id",
peewee.IntegerField(default=None, index=True),
)
# Changes on Webhook Table # Changes on Webhook Table
migrator.alter_column_type( migrator.alter_column_type(
Webhooks, Webhooks,

View File

@ -6,7 +6,6 @@ import logging
from app.classes.shared.console import Console from app.classes.shared.console import Console
from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.shared.migration import Migrator, MigrateHistory
from app.classes.models.management import ( from app.classes.models.management import (
AuditLog,
Webhooks, Webhooks,
Schedules, Schedules,
Backups, Backups,
@ -73,20 +72,6 @@ def migrate(migrator: Migrator, database, **kwargs):
try: try:
logger.info("Migrating Data from Int to UUID (Foreign Keys)") logger.info("Migrating Data from Int to UUID (Foreign Keys)")
Console.info("Migrating Data from Int to UUID (Foreign Keys)") Console.info("Migrating Data from Int to UUID (Foreign Keys)")
# Changes on Audit Log Table
for audit_log in AuditLog.select():
old_server_id = audit_log.server_id_id
if old_server_id == "0" or old_server_id is None:
server_uuid = None
else:
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
AuditLog.update(server_id=server_uuid).where(
AuditLog.audit_id == audit_log.audit_id
).execute()
# Changes on Webhooks Log Table # Changes on Webhooks Log Table
for webhook in Webhooks.select(): for webhook in Webhooks.select():
@ -247,21 +232,6 @@ def rollback(migrator: Migrator, database, **kwargs):
try: try:
logger.info("Migrating Data from UUID to Int (Foreign Keys)") logger.info("Migrating Data from UUID to Int (Foreign Keys)")
Console.info("Migrating Data from UUID to Int (Foreign Keys)") Console.info("Migrating Data from UUID to Int (Foreign Keys)")
# Changes on Audit Log Table
for audit_log in AuditLog.select():
old_server_id = audit_log.server_id_id
if old_server_id is None:
new_server_id = 0
else:
try:
server = Servers.get_or_none(Servers.server_uuid == old_server_id)
new_server_id = server.server_id
except:
new_server_id = old_server_id
AuditLog.update(server_id=new_server_id).where(
AuditLog.audit_id == audit_log.audit_id
).execute()
# Changes on Webhooks Log Table # Changes on Webhooks Log Table
for webhook in Webhooks.select(): for webhook in Webhooks.select():
old_server_id = webhook.server_id_id old_server_id = webhook.server_id_id

View File

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

View File

@ -0,0 +1,34 @@
import peewee
import datetime
from peewee import (
AutoField,
DateTimeField,
CharField,
IntegerField,
ForeignKeyField,
TextField,
)
from app.classes.shared.server import Servers
def migrate(migrator, db):
migrator.drop_table("audit_log")
def rollback(migrator, db):
class AuditLog(peewee.Model):
audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="")
user_id = IntegerField(default=0, index=True)
source_ip = CharField(default="127.0.0.1")
server_id = ForeignKeyField(
Servers, backref="audit_server", null=True
) # When auditing global events, use server ID null
log_msg = TextField(default="")
class Meta:
table_name = "audit_log"
migrator.create_table(AuditLog)

View File

@ -20,6 +20,7 @@
"created": "Vytvořen", "created": "Vytvořen",
"deleteKeyConfirmation": "Chcete tento API klíč odstranit? Tuto akci nelze vrátit zpět.", "deleteKeyConfirmation": "Chcete tento API klíč odstranit? Tuto akci nelze vrátit zpět.",
"deleteKeyConfirmationTitle": "Odstranit klíč API ${keyId}?", "deleteKeyConfirmationTitle": "Odstranit klíč API ${keyId}?",
"fullAccess": "všechno",
"getToken": "Získat token", "getToken": "Získat token",
"name": "Jméno", "name": "Jméno",
"nameDesc": "Jak chcete nazvat tento token API? ", "nameDesc": "Jak chcete nazvat tento token API? ",
@ -116,6 +117,7 @@
"welcome": "Vítejte v Crafty Controlleru" "welcome": "Vítejte v Crafty Controlleru"
}, },
"datatables": { "datatables": {
"loadingRecords": "Načítání...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktivace řazení sloupce vzestupně", "sortAscending": ": aktivace řazení sloupce vzestupně",
@ -218,6 +220,7 @@
"not-downloaded": "Zdá se, že nemůžeme najít váš spustitelný soubor. Bylo jeho stahování dokončeno? Jsou oprávnění nastavena na spustitelný soubor?", "not-downloaded": "Zdá se, že nemůžeme najít váš spustitelný soubor. Bylo jeho stahování dokončeno? Jsou oprávnění nastavena na spustitelný soubor?",
"portReminder": "Zjistili jsme, že server {} byl spuštěn poprvé. Ujistěte se, že jste přesměrovali port {} přes váš směrovač/firewall, aby byl tento port vzdáleně přístupný z internetu.", "portReminder": "Zjistili jsme, že server {} byl spuštěn poprvé. Ujistěte se, že jste přesměrovali port {} přes váš směrovač/firewall, aby byl tento port vzdáleně přístupný z internetu.",
"privMsg": "a ", "privMsg": "a ",
"return": "vrátit se na hlavní stránku",
"serverJars1": "Server JAR api je nepřístupná. Prosím zkontrolujte", "serverJars1": "Server JAR api je nepřístupná. Prosím zkontrolujte",
"serverJars2": "pro aktualní informace.", "serverJars2": "pro aktualní informace.",
"start-error": "Server {} se nepodařilo spustit s kódem chyby: {}", "start-error": "Server {} se nepodařilo spustit s kódem chyby: {}",
@ -612,7 +615,7 @@
"credits": "Zásluhy", "credits": "Zásluhy",
"dashboard": "Ovládací panel", "dashboard": "Ovládací panel",
"documentation": "Dokumentace", "documentation": "Dokumentace",
"inApp": "V app dokumentaci", "inApp": "V lokalní dokumentaci",
"navigation": "Navigace", "navigation": "Navigace",
"newServer": "Vytvořit nový server", "newServer": "Vytvořit nový server",
"servers": "Servery" "servers": "Servery"

View File

@ -20,6 +20,7 @@
"created": "Erstellt", "created": "Erstellt",
"deleteKeyConfirmation": "Möchten Sie diesen API Schlüssel löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "deleteKeyConfirmation": "Möchten Sie diesen API Schlüssel löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"deleteKeyConfirmationTitle": "Folgenden API Schlüssel löschen: ${keyId}?", "deleteKeyConfirmationTitle": "Folgenden API Schlüssel löschen: ${keyId}?",
"fullAccess": "Vollzugriff",
"getToken": "Schlüssel erhalten", "getToken": "Schlüssel erhalten",
"name": "Name", "name": "Name",
"nameDesc": "Wie soll der API Schlüssel genannt werden? ", "nameDesc": "Wie soll der API Schlüssel genannt werden? ",
@ -116,6 +117,7 @@
"welcome": "Willkommen bei Crafty Controller" "welcome": "Willkommen bei Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Laden...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": Aktivieren, um die Spalte aufsteigend zu sortieren", "sortAscending": ": Aktivieren, um die Spalte aufsteigend zu sortieren",
@ -203,6 +205,7 @@
"not-downloaded": "Crafty kann die auszuführende Datei nicht finden. Ist der Download abgeschlossen? Sind die Berechtigungen für Crafty korrekt?", "not-downloaded": "Crafty kann die auszuführende Datei nicht finden. Ist der Download abgeschlossen? Sind die Berechtigungen für Crafty korrekt?",
"portReminder": "Wir haben festgestellt, dass dies das erste Mal ist, dass {} ausgeführt wurde. Stellen Sie sicher, dass Sie Port {} durch Ihren Router/Firewall weiterleiten, um den Fernzugriff aus dem Internet zu ermöglichen.", "portReminder": "Wir haben festgestellt, dass dies das erste Mal ist, dass {} ausgeführt wurde. Stellen Sie sicher, dass Sie Port {} durch Ihren Router/Firewall weiterleiten, um den Fernzugriff aus dem Internet zu ermöglichen.",
"privMsg": "und der/die/das ", "privMsg": "und der/die/das ",
"return": "Zurück zum Dashboard",
"serverJars1": "Server-JAR-API nicht erreichbar. Bitte überprüfen Sie ", "serverJars1": "Server-JAR-API nicht erreichbar. Bitte überprüfen Sie ",
"serverJars2": "um die aktuellsten Informationen zu erhalten.", "serverJars2": "um die aktuellsten Informationen zu erhalten.",
"start-error": "Der Server {} konnte wegen dem Fehlercode: {} nicht gestartet werden", "start-error": "Der Server {} konnte wegen dem Fehlercode: {} nicht gestartet werden",

View File

@ -20,6 +20,7 @@
"created": "Created", "created": "Created",
"deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.", "deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.",
"deleteKeyConfirmationTitle": "Remove API key ${keyId}?", "deleteKeyConfirmationTitle": "Remove API key ${keyId}?",
"fullAccess": "Full Access",
"getToken": "Get A Token", "getToken": "Get A Token",
"name": "Name", "name": "Name",
"nameDesc": "What would you like to call this API token? ", "nameDesc": "What would you like to call this API token? ",
@ -28,7 +29,6 @@
"permName": "Permission Name", "permName": "Permission Name",
"perms": "Permissions", "perms": "Permissions",
"server": "Server: ", "server": "Server: ",
"superUser": "Super User",
"yes": "Yes" "yes": "Yes"
}, },
"base": { "base": {
@ -116,6 +116,7 @@
"welcome": "Welcome to Crafty Controller" "welcome": "Welcome to Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Loading...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activate to sort column ascending", "sortAscending": ": activate to sort column ascending",
@ -203,6 +204,7 @@
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?", "not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.", "portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"privMsg": "and the ", "privMsg": "and the ",
"return": "Return to Dashboard",
"serverJars1": "Server JARs API unreachable. Please check", "serverJars1": "Server JARs API unreachable. Please check",
"serverJars2": "for the most up to date information.", "serverJars2": "for the most up to date information.",
"start-error": "Server {} failed to start with error code: {}", "start-error": "Server {} failed to start with error code: {}",

View File

@ -20,6 +20,7 @@
"created": "Creado", "created": "Creado",
"deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.", "deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.",
"deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?", "deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?",
"fullAccess": "Acceso completo",
"getToken": "Conseguir un Token", "getToken": "Conseguir un Token",
"name": "Nombre", "name": "Nombre",
"nameDesc": "¿Como te gustaría llamar a este Token de API? ", "nameDesc": "¿Como te gustaría llamar a este Token de API? ",
@ -116,6 +117,7 @@
"welcome": "Bienvenido a Crafty Controller" "welcome": "Bienvenido a Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Cargando...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activar para ordenar las columnas de manera ascendente", "sortAscending": ": activar para ordenar las columnas de manera ascendente",
@ -203,6 +205,7 @@
"not-downloaded": "No podemos encontrar el archivo ejecutable. ¿Ha terminado de descargarse? ¿Están los permisos puestos como ejecutable?", "not-downloaded": "No podemos encontrar el archivo ejecutable. ¿Ha terminado de descargarse? ¿Están los permisos puestos como ejecutable?",
"portReminder": "Detectamos que es la primera vez que se inicia {}. Asegúrese de configurar el puerto {} a través de su router/firewall para hacer el servidor accesible por Internet.", "portReminder": "Detectamos que es la primera vez que se inicia {}. Asegúrese de configurar el puerto {} a través de su router/firewall para hacer el servidor accesible por Internet.",
"privMsg": "y el ", "privMsg": "y el ",
"return": "Volver al panel de control",
"serverJars1": "API de Servidor JAR no disponible. por favor, compruebe", "serverJars1": "API de Servidor JAR no disponible. por favor, compruebe",
"serverJars2": "para la información más actualizada.", "serverJars2": "para la información más actualizada.",
"start-error": "Servidor {} fallo al iniciar con código de error: {}", "start-error": "Servidor {} fallo al iniciar con código de error: {}",

View File

@ -100,6 +100,7 @@
"welcome": "Tervetuloa Crafty Controller" "welcome": "Tervetuloa Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Ladataan...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": lajittele sarake nousevasti", "sortAscending": ": lajittele sarake nousevasti",

View File

@ -20,6 +20,7 @@
"created": "Crée", "created": "Crée",
"deleteKeyConfirmation": "Es-tu sûr de vouloir supprimer cette clé API? Tu ne pourras plus revenir en arrière.", "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}?", "deleteKeyConfirmationTitle": "Supprimer la clé API ${keyId}?",
"fullAccess": "Accès Complet",
"getToken": "Obtenir un Jeton", "getToken": "Obtenir un Jeton",
"name": "Nom", "name": "Nom",
"nameDesc": "Comment appeler ce Jeton d'API ? ", "nameDesc": "Comment appeler ce Jeton d'API ? ",
@ -116,6 +117,7 @@
"welcome": "Bienvenue sur Crafty Controller" "welcome": "Bienvenue sur Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Chargement ...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activer pour trier les colonnes dans l'ordre croissant", "sortAscending": ": activer pour trier les colonnes dans l'ordre croissant",
@ -203,6 +205,7 @@
"not-downloaded": "Nous ne parvenons pas à trouver le fichier exécutable. A-t-il fini de Télécharger ? Les permissions permettent elles l'exécution ?", "not-downloaded": "Nous ne parvenons pas à trouver le fichier exécutable. A-t-il fini de Télécharger ? Les permissions permettent elles l'exécution ?",
"portReminder": "Nous avons détecté que c'est la première fois que {} est exécuté. Assurez-vous de transférer le port {} via votre routeur/pare-feu pour le rendre accessible à distance depuis Internet.", "portReminder": "Nous avons détecté que c'est la première fois que {} est exécuté. Assurez-vous de transférer le port {} via votre routeur/pare-feu pour le rendre accessible à distance depuis Internet.",
"privMsg": "et le ", "privMsg": "et le ",
"return": "Revenir au Tableau de Bord",
"serverJars1": "l'API Server JARs est inaccessible. Merci de vérifier", "serverJars1": "l'API Server JARs est inaccessible. Merci de vérifier",
"serverJars2": "pour les informations les plus à jour.", "serverJars2": "pour les informations les plus à jour.",
"start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}", "start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}",

View File

@ -99,6 +99,7 @@
"welcome": "Wolkom by Crafty Controller" "welcome": "Wolkom by Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Laden...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktivearje om kolom oprinnend te sortearjen", "sortAscending": ": aktivearje om kolom oprinnend te sortearjen",

View File

@ -20,6 +20,7 @@
"created": "נוצר", "created": "נוצר",
"deleteKeyConfirmation": "האם ברצונך למחוק מפתח API זה? אי אפשר לבטל את זה.", "deleteKeyConfirmation": "האם ברצונך למחוק מפתח API זה? אי אפשר לבטל את זה.",
"deleteKeyConfirmationTitle": "? ${keyId} API-להסיר את מפתח ה", "deleteKeyConfirmationTitle": "? ${keyId} API-להסיר את מפתח ה",
"fullAccess": "גישה מלאה להכל",
"getToken": "קבלת אסימון", "getToken": "קבלת אסימון",
"name": "שם", "name": "שם",
"nameDesc": "הזה API-איך תרצו לקרוא לאסימון ה", "nameDesc": "הזה API-איך תרצו לקרוא לאסימון ה",
@ -116,6 +117,7 @@
"welcome": "ברוכים הבאים ל-פאנל קראפטי" "welcome": "ברוכים הבאים ל-פאנל קראפטי"
}, },
"datatables": { "datatables": {
"loadingRecords": "...טוען",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": הפעילו כדי למיין עמודות בסדר עולה", "sortAscending": ": הפעילו כדי למיין עמודות בסדר עולה",
@ -203,6 +205,7 @@
"not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?", "not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?",
"portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.", "portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.",
"privMsg": "וה", "privMsg": "וה",
"return": "חזרה לפאנל",
"serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק", "serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק",
"serverJars2": "למידע מעודכן ביותר.", "serverJars2": "למידע מעודכן ביותר.",
"start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}", "start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}",

View File

@ -99,6 +99,7 @@
"welcome": "Dobrodošli u Crafty Controller" "welcome": "Dobrodošli u Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Učitavanje...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktiviraj za sortiranje stupca uzlazno", "sortAscending": ": aktiviraj za sortiranje stupca uzlazno",

View File

@ -100,6 +100,7 @@
"welcome": "Selamat Datang Di Crafty Controller" "welcome": "Selamat Datang Di Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Loading...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktifkan untuk mengurutkan kolom menaik", "sortAscending": ": aktifkan untuk mengurutkan kolom menaik",

View File

@ -20,6 +20,7 @@
"created": "Creato", "created": "Creato",
"deleteKeyConfirmation": "Vuoi cancellare questa chiave API? Non puoi tornare indietro.", "deleteKeyConfirmation": "Vuoi cancellare questa chiave API? Non puoi tornare indietro.",
"deleteKeyConfirmationTitle": "Rimuovere la chiave API ${keyId}?", "deleteKeyConfirmationTitle": "Rimuovere la chiave API ${keyId}?",
"fullAccess": " Accesso completo",
"getToken": "Prendi un Token", "getToken": "Prendi un Token",
"name": "Nome", "name": "Nome",
"nameDesc": "Come desideri chiamare questo Token API? ", "nameDesc": "Come desideri chiamare questo Token API? ",
@ -116,6 +117,7 @@
"welcome": "Benvenuto su Crafty Controller" "welcome": "Benvenuto su Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Carico...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": attiva per ordinare le colonne in modo ascendente", "sortAscending": ": attiva per ordinare le colonne in modo ascendente",
@ -203,6 +205,7 @@
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?", "not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.", "portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"privMsg": "e il ", "privMsg": "e il ",
"return": "Torna alla pagina iniziale",
"serverJars1": "API JAR del server non raggiungibile. Si prega di controllare", "serverJars1": "API JAR del server non raggiungibile. Si prega di controllare",
"serverJars2": "per informazioni più aggiornate.", "serverJars2": "per informazioni più aggiornate.",
"start-error": "Server {} failed to start with error code: {}", "start-error": "Server {} failed to start with error code: {}",

View File

@ -20,6 +20,7 @@
"created": "CREATED", "created": "CREATED",
"deleteKeyConfirmation": "U SURE U WANTZ TO DELETE DIS? CAN'T UNDO!", "deleteKeyConfirmation": "U SURE U WANTZ TO DELETE DIS? CAN'T UNDO!",
"deleteKeyConfirmationTitle": "I CAN EATZ IT??? : ${keyId}?", "deleteKeyConfirmationTitle": "I CAN EATZ IT??? : ${keyId}?",
"fullAccess": "All da Doors Open",
"getToken": "GIT TOKEN", "getToken": "GIT TOKEN",
"name": "NAME", "name": "NAME",
"nameDesc": "WUT WUD U LIEK 2 CALL DIS API TOKEN? ", "nameDesc": "WUT WUD U LIEK 2 CALL DIS API TOKEN? ",
@ -116,6 +117,7 @@
"welcome": "WELCOM 2 CWAFTY CONTROLLR" "welcome": "WELCOM 2 CWAFTY CONTROLLR"
}, },
"datatables": { "datatables": {
"loadingRecords": "Loading...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activate to sort column ascending", "sortAscending": ": activate to sort column ascending",
@ -203,6 +205,7 @@
"not-downloaded": "SOZ BUT I FAILDZ CAN'T SEEM TO FINDZ YOUR FISH. PLZ GIB MEZ IT. I HUNGRY.", "not-downloaded": "SOZ BUT I FAILDZ CAN'T SEEM TO FINDZ YOUR FISH. PLZ GIB MEZ IT. I HUNGRY.",
"portReminder": "WE HAS DETECTD DIS AR TEH FURST TIEM {} IZ BEAN RUN. IF U WANTS IT ACESIBLE TO NEIGHBORHOOD CATS PLZ UNLOCK CAT_FLAP, {}, THRU UR ROUTR IF U HAS NOT DUN SO.", "portReminder": "WE HAS DETECTD DIS AR TEH FURST TIEM {} IZ BEAN RUN. IF U WANTS IT ACESIBLE TO NEIGHBORHOOD CATS PLZ UNLOCK CAT_FLAP, {}, THRU UR ROUTR IF U HAS NOT DUN SO.",
"privMsg": "AND THEEZ ", "privMsg": "AND THEEZ ",
"return": "Go Bak to Dashbored",
"serverJars1": "CAN'T TALK TO SERVER JARS API. CHECKZ", "serverJars1": "CAN'T TALK TO SERVER JARS API. CHECKZ",
"serverJars2": "TO SEE NEWZ STUFFZ.", "serverJars2": "TO SEE NEWZ STUFFZ.",
"start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}", "start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}",

View File

@ -20,6 +20,7 @@
"created": "Izveidots", "created": "Izveidots",
"deleteKeyConfirmation": "Vai vēlies dzēst šo API atslēgu? Šo nevar atdarīt.", "deleteKeyConfirmation": "Vai vēlies dzēst šo API atslēgu? Šo nevar atdarīt.",
"deleteKeyConfirmationTitle": "Noņemt API atslēgu ${keyId}?", "deleteKeyConfirmationTitle": "Noņemt API atslēgu ${keyId}?",
"fullAccess": "Pilna piekļuve",
"getToken": "Saņemt Pilnvaru (Token)", "getToken": "Saņemt Pilnvaru (Token)",
"name": "Nosaukums", "name": "Nosaukums",
"nameDesc": "Kā jūs vēlaties nosaukt šo Pilnvaru (Token)? ", "nameDesc": "Kā jūs vēlaties nosaukt šo Pilnvaru (Token)? ",
@ -117,6 +118,7 @@
"welcome": "Esiet sveicināts Crafty Controller" "welcome": "Esiet sveicināts Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Ielādē...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktivizēt lai kārotu kolonnu augoši", "sortAscending": ": aktivizēt lai kārotu kolonnu augoši",
@ -204,6 +206,7 @@
"not-downloaded": "Mēs nevaram atrast jūsu izpildāmo failu. Vai tas ir beidzis lejupielādēties? Vai tā peikļuves ir uzstādītas kā palaižamas?", "not-downloaded": "Mēs nevaram atrast jūsu izpildāmo failu. Vai tas ir beidzis lejupielādēties? Vai tā peikļuves ir uzstādītas kā palaižamas?",
"portReminder": "Mēs noteicām ka šī ir pirmā reize, kad {} ir ticis palaists. Pārliecinies izlaist portu {} cauri savam rūterim/ugunsmūrim lai padarītu šo attāli pieejamu no interneta.", "portReminder": "Mēs noteicām ka šī ir pirmā reize, kad {} ir ticis palaists. Pārliecinies izlaist portu {} cauri savam rūterim/ugunsmūrim lai padarītu šo attāli pieejamu no interneta.",
"privMsg": "un ", "privMsg": "un ",
"return": "Atgriezties uz pārskatu",
"serverJars1": "Serveru JAR API nav sasniedzams. Lūdzu pārbaudiet", "serverJars1": "Serveru JAR API nav sasniedzams. Lūdzu pārbaudiet",
"serverJars2": "priekš jaunākās informācijas.", "serverJars2": "priekš jaunākās informācijas.",
"start-error": "Serveris {} neveiskmīgi startējās ar kļūdas kodu: {}", "start-error": "Serveris {} neveiskmīgi startējās ar kļūdas kodu: {}",

View File

@ -20,6 +20,7 @@
"created": "Gecreëerd", "created": "Gecreëerd",
"deleteKeyConfirmation": "Wilt u deze API sleutel verwijderen? Dit kan niet ongedaan gemaakt worden.", "deleteKeyConfirmation": "Wilt u deze API sleutel verwijderen? Dit kan niet ongedaan gemaakt worden.",
"deleteKeyConfirmationTitle": "API sleutel verwijderen ${keyId}?", "deleteKeyConfirmationTitle": "API sleutel verwijderen ${keyId}?",
"fullAccess": "Volledige toegang",
"getToken": "Verkrijg een Token", "getToken": "Verkrijg een Token",
"name": "Naam", "name": "Naam",
"nameDesc": "Hoe wilt u dit API token noemen? ", "nameDesc": "Hoe wilt u dit API token noemen? ",
@ -116,6 +117,7 @@
"welcome": "Welkom bij Crafty Controller " "welcome": "Welkom bij Crafty Controller "
}, },
"datatables": { "datatables": {
"loadingRecords": "Bezig met laden...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activeren om kolom oplopend te sorteren", "sortAscending": ": activeren om kolom oplopend te sorteren",
@ -203,6 +205,7 @@
"not-downloaded": "We kunnen uw uitvoerbare bestand niet vinden. Is het klaar met downloaden? Zijn de rechten ingesteld op uitvoerbaar?", "not-downloaded": "We kunnen uw uitvoerbare bestand niet vinden. Is het klaar met downloaden? Zijn de rechten ingesteld op uitvoerbaar?",
"portReminder": "We hebben ontdekt dat dit de eerste keer is dat {} wordt uitgevoerd. Zorg ervoor dat u poort {} doorstuurt via uw router/firewall om deze op afstand toegankelijk te maken vanaf het internet.", "portReminder": "We hebben ontdekt dat dit de eerste keer is dat {} wordt uitgevoerd. Zorg ervoor dat u poort {} doorstuurt via uw router/firewall om deze op afstand toegankelijk te maken vanaf het internet.",
"privMsg": "en de ", "privMsg": "en de ",
"return": "Terug naar Dashboard",
"serverJars1": "Server JARs API niet bereikbaar. Controleer alstublieft", "serverJars1": "Server JARs API niet bereikbaar. Controleer alstublieft",
"serverJars2": "voor de meest recente informatie.", "serverJars2": "voor de meest recente informatie.",
"start-error": "Server {} kan niet starten met foutcode: {}", "start-error": "Server {} kan niet starten met foutcode: {}",

View File

@ -99,6 +99,7 @@
"welcome": "Welkom bij Crafty Controller" "welcome": "Welkom bij Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Laden...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": activeer om kolom oplopend te sorteren", "sortAscending": ": activeer om kolom oplopend te sorteren",

View File

@ -20,6 +20,7 @@
"created": "Stworzono", "created": "Stworzono",
"deleteKeyConfirmation": "Czy chcesz usunąć ten klucz API? Nie można tego cofnąć.", "deleteKeyConfirmation": "Czy chcesz usunąć ten klucz API? Nie można tego cofnąć.",
"deleteKeyConfirmationTitle": "Usunąć Klucz API ${keyId}?", "deleteKeyConfirmationTitle": "Usunąć Klucz API ${keyId}?",
"fullAccess": "Pełny dostęp",
"getToken": "Zdobądź token", "getToken": "Zdobądź token",
"name": "Nazwa", "name": "Nazwa",
"nameDesc": "Jak chcesz nazwać ten klucz API? ", "nameDesc": "Jak chcesz nazwać ten klucz API? ",
@ -116,6 +117,7 @@
"welcome": "Witamy w Crafty Controller" "welcome": "Witamy w Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Wczytywanie...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": aktywuj, aby sortować kolumny w góre", "sortAscending": ": aktywuj, aby sortować kolumny w góre",
@ -203,6 +205,7 @@
"not-downloaded": "Nie możemy znaleść twojego pliku serwera. Czy skończył się pobierać? Czy permisje są ustawione na wykonywanle?", "not-downloaded": "Nie możemy znaleść twojego pliku serwera. Czy skończył się pobierać? Czy permisje są ustawione na wykonywanle?",
"portReminder": "Zauważyliśmy że to jest pierwszy raz {} kiedy był włączony. Upewnij się że otworzyłeś port {} na swoim routerze/firewallu aby korzystać z tego poza domem.", "portReminder": "Zauważyliśmy że to jest pierwszy raz {} kiedy był włączony. Upewnij się że otworzyłeś port {} na swoim routerze/firewallu aby korzystać z tego poza domem.",
"privMsg": "i także ", "privMsg": "i także ",
"return": "Powrót do panelu",
"serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź", "serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź",
"serverJars2": "dla najnowzsych informacji.", "serverJars2": "dla najnowzsych informacji.",
"start-error": "Serwer {} nie mógł się odpalić z powodu: {}", "start-error": "Serwer {} nie mógł się odpalić z powodu: {}",

View File

@ -100,6 +100,7 @@
"welcome": "Bem-vindo ao Crafty Controller" "welcome": "Bem-vindo ao Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Carregando...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": ative para ordenar a coluna de forma ascendente", "sortAscending": ": ative para ordenar a coluna de forma ascendente",

View File

@ -20,6 +20,7 @@
"created": "สร้างเมื่อ", "created": "สร้างเมื่อ",
"deleteKeyConfirmation": "คุณต้องการลบคีย์ API นี้หรือไม่ สิ่งนี้ไม่สามารถยกเลิกได้", "deleteKeyConfirmation": "คุณต้องการลบคีย์ API นี้หรือไม่ สิ่งนี้ไม่สามารถยกเลิกได้",
"deleteKeyConfirmationTitle": "ลบคีย์ API นี้ ${keyId} หรือไม่?", "deleteKeyConfirmationTitle": "ลบคีย์ API นี้ ${keyId} หรือไม่?",
"fullAccess": "เข้าถึงได้ทั้งหมด",
"getToken": "แสดงโทเค็น", "getToken": "แสดงโทเค็น",
"name": "ชื่อ", "name": "ชื่อ",
"nameDesc": "คุณต้องการเรียกโทเค็น API นี้ว่าอะไร ? ", "nameDesc": "คุณต้องการเรียกโทเค็น API นี้ว่าอะไร ? ",
@ -116,6 +117,7 @@
"welcome": "ยินดีต้อนรับสู่ Crafty Controller" "welcome": "ยินดีต้อนรับสู่ Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "กำลังโหลด...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": เปิดใช้งานเพื่อเรียงลำดับคอลัมน์จากน้อยไปมาก", "sortAscending": ": เปิดใช้งานเพื่อเรียงลำดับคอลัมน์จากน้อยไปมาก",
@ -203,6 +205,7 @@
"not-downloaded": "ดูเหมือนว่าเราจะไม่พบแฟ้มกระทำการของคุณ (.jar) ตรวจสอบให้แน่ใจว่าการดาวโหลดน์เสร็จสิ้นแล้ว, การอนุญาตถูกตั้งไปยังแฟ้มกระทำการหรือไม่?", "not-downloaded": "ดูเหมือนว่าเราจะไม่พบแฟ้มกระทำการของคุณ (.jar) ตรวจสอบให้แน่ใจว่าการดาวโหลดน์เสร็จสิ้นแล้ว, การอนุญาตถูกตั้งไปยังแฟ้มกระทำการหรือไม่?",
"portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล", "portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล",
"privMsg": "และ ", "privMsg": "และ ",
"return": "ย้อนกลับไปยังแผงควบคุม",
"serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ", "serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ",
"serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด", "serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด",
"start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}", "start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}",

View File

@ -20,6 +20,7 @@
"created": "Oluşturuldu", "created": "Oluşturuldu",
"deleteKeyConfirmation": "Bu API anahtarını silmek istediğine emin misin? Bu geri alınamaz.", "deleteKeyConfirmation": "Bu API anahtarını silmek istediğine emin misin? Bu geri alınamaz.",
"deleteKeyConfirmationTitle": "${keyId} API anahtarını kaldırma işlemi.", "deleteKeyConfirmationTitle": "${keyId} API anahtarını kaldırma işlemi.",
"fullAccess": "Tam Erişim",
"getToken": "Bir Token Al", "getToken": "Bir Token Al",
"name": "Ad", "name": "Ad",
"nameDesc": "Bu API tokeninin adı ne olsun?", "nameDesc": "Bu API tokeninin adı ne olsun?",
@ -116,6 +117,7 @@
"welcome": "Crafty Controller'a Hoşgeldiniz!" "welcome": "Crafty Controller'a Hoşgeldiniz!"
}, },
"datatables": { "datatables": {
"loadingRecords": "Yükleniyor...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": artan sütun sıralamasını aktifleştir", "sortAscending": ": artan sütun sıralamasını aktifleştir",
@ -203,6 +205,7 @@
"not-downloaded": "Çalıştırılabilir dosyanızı bulamıyoruz. İndirme işlemi tamamlandı mı? İzinler çalıştırılabilir olarak ayarlandı mı?", "not-downloaded": "Çalıştırılabilir dosyanızı bulamıyoruz. İndirme işlemi tamamlandı mı? İzinler çalıştırılabilir olarak ayarlandı mı?",
"portReminder": "{} ilk kez çalıştırılıyor olduğunu tespit ettik. Bunu internetten uzaktan erişilebilir kılmak için {} bağlantı noktasını yönlendiriciniz/güvenlik duvarınız üzerinden ilettiğinizden emin olun.", "portReminder": "{} ilk kez çalıştırılıyor olduğunu tespit ettik. Bunu internetten uzaktan erişilebilir kılmak için {} bağlantı noktasını yönlendiriciniz/güvenlik duvarınız üzerinden ilettiğinizden emin olun.",
"privMsg": "ve ", "privMsg": "ve ",
"return": "Arayüze Geri Dön",
"serverJars1": "Sunucu JARs API'ına erişilemiyor.", "serverJars1": "Sunucu JARs API'ına erişilemiyor.",
"serverJars2": "en güncel bilgilere sahiptir", "serverJars2": "en güncel bilgilere sahiptir",
"start-error": "{} sunucusu başlamatılamadı. Hata kodu: {}", "start-error": "{} sunucusu başlamatılamadı. Hata kodu: {}",

View File

@ -20,6 +20,7 @@
"created": "Створений", "created": "Створений",
"deleteKeyConfirmation": "Ви дійсно бажаєте видалити API ключ? Це незворотня дія.", "deleteKeyConfirmation": "Ви дійсно бажаєте видалити API ключ? Це незворотня дія.",
"deleteKeyConfirmationTitle": "Видалення API ключ ${keyId}?", "deleteKeyConfirmationTitle": "Видалення API ключ ${keyId}?",
"fullAccess": "Повний доступ",
"getToken": "Отримати Токен", "getToken": "Отримати Токен",
"name": "Ім'я", "name": "Ім'я",
"nameDesc": "Як ви хочете назвати даний API токен?", "nameDesc": "Як ви хочете назвати даний API токен?",
@ -116,6 +117,7 @@
"welcome": "Ласкаво просимо у Crafty Controller" "welcome": "Ласкаво просимо у Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "Завантаження...",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ": активуйте, щоб сортувати стовпці за зростанням", "sortAscending": ": активуйте, щоб сортувати стовпці за зростанням",
@ -203,6 +205,7 @@
"not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?", "not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?",
"portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.", "portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.",
"privMsg": "і ", "privMsg": "і ",
"return": "Повернутись до панелі",
"serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте", "serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте",
"serverJars2": "для найактуальнішої інформації.", "serverJars2": "для найактуальнішої інформації.",
"start-error": "Сервер {} не запустився через помилку: {}", "start-error": "Сервер {} не запустився через помилку: {}",

View File

@ -20,6 +20,7 @@
"created": "创建时间", "created": "创建时间",
"deleteKeyConfirmation": "您想要删除这个 API 密钥吗?此操作不能撤销。", "deleteKeyConfirmation": "您想要删除这个 API 密钥吗?此操作不能撤销。",
"deleteKeyConfirmationTitle": "删除 API 密钥 ${keyId}", "deleteKeyConfirmationTitle": "删除 API 密钥 ${keyId}",
"fullAccess": "完全访问",
"getToken": "获得一个令牌", "getToken": "获得一个令牌",
"name": "名称", "name": "名称",
"nameDesc": "你想把这个 API 令牌叫做什么?", "nameDesc": "你想把这个 API 令牌叫做什么?",
@ -116,6 +117,7 @@
"welcome": "欢迎来到 Crafty Controller" "welcome": "欢迎来到 Crafty Controller"
}, },
"datatables": { "datatables": {
"loadingRecords": "正在加载……",
"i18n": { "i18n": {
"aria": { "aria": {
"sortAscending": ":激活对队列的升序排列", "sortAscending": ":激活对队列的升序排列",
@ -203,6 +205,7 @@
"not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?", "not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?",
"portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。", "portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。",
"privMsg": "以及", "privMsg": "以及",
"return": "返回仪表板",
"serverJars1": "无法访问服务器 JAR API。请检查", "serverJars1": "无法访问服务器 JAR API。请检查",
"serverJars2": "以获取最新信息。", "serverJars2": "以获取最新信息。",
"start-error": "服务器 {} 启动失败,错误代码为:{}", "start-error": "服务器 {} 启动失败,错误代码为:{}",

View File

@ -17,6 +17,7 @@ from app.classes.models.users import HelperUsers
from app.classes.models.management import HelpersManagement from app.classes.models.management import HelpersManagement
from app.classes.shared.import_helper import ImportHelpers from app.classes.shared.import_helper import ImportHelpers
from app.classes.shared.websocket_manager import WebSocketManager from app.classes.shared.websocket_manager import WebSocketManager
from app.classes.logging.log_formatter import JsonFormatter
console = Console() console = Console()
helper = Helpers() helper = Helpers()
@ -284,6 +285,11 @@ def setup_logging(debug=True):
logging.config.dictConfig(logging_config) logging.config.dictConfig(logging_config)
# Apply JSON formatting to the "audit" handler
for handler in logging.getLogger().handlers:
if handler.name == "audit_log_handler":
handler.setFormatter(JsonFormatter())
else: else:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.warning(f"Unable to read logging config from {logging_config_file}") logging.warning(f"Unable to read logging config from {logging_config_file}")