mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into refactor/backups
This commit is contained in:
commit
6ca396854d
@ -5,7 +5,7 @@ yamllint:
|
||||
stage: lint
|
||||
image: registry.gitlab.com/pipeline-components/yamllint:latest
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
@ -18,7 +18,7 @@ jsonlint:
|
||||
stage: lint
|
||||
image: registry.gitlab.com/pipeline-components/jsonlint:latest
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
@ -33,7 +33,7 @@ black:
|
||||
stage: lint
|
||||
image: registry.gitlab.com/pipeline-components/black:latest
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
@ -46,7 +46,7 @@ pylint:
|
||||
stage: lint
|
||||
image: registry.gitlab.com/pipeline-components/pylint:latest
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
@ -69,7 +69,7 @@ sonarcloud-check:
|
||||
name: sonarsource/sonar-scanner-cli:latest
|
||||
entrypoint: [""]
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$SONAR_TOKEN == null"
|
||||
when: never
|
||||
@ -91,7 +91,7 @@ lang-check:
|
||||
stage: lint
|
||||
image: alpine:latest
|
||||
tags:
|
||||
- docker
|
||||
- saas-linux-medium-amd64
|
||||
rules:
|
||||
- if: "$CODE_QUALITY_DISABLED"
|
||||
when: never
|
||||
|
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,5 +1,5 @@
|
||||
# Changelog
|
||||
## --- [4.3.3] - 2024/TBD
|
||||
## --- [4.4.1] - 2024/TBD
|
||||
### New features
|
||||
TBD
|
||||
### Bug fixes
|
||||
@ -10,6 +10,20 @@ TBD
|
||||
TBD
|
||||
<br><br>
|
||||
|
||||
## --- [4.4.0] - 2024/05/11
|
||||
### Refactor
|
||||
- Refactor API keys "super user" to "full access" ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/731) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/759))
|
||||
- Refactor SBuilder to use Big Bucket Svc ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/755))
|
||||
### Bug fixes
|
||||
- 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
|
||||
- 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
|
||||
- Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749))
|
||||
<br><br>
|
||||
|
||||
## --- [4.3.2] - 2024/04/07
|
||||
### Refactor
|
||||
- Refactor ServerJars caching and move to api.serverjars.com ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/744) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/746))
|
||||
|
@ -1,5 +1,5 @@
|
||||
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
||||
# Crafty Controller 4.3.3
|
||||
# Crafty Controller 4.4.1
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
|
@ -95,9 +95,6 @@ class ManagementController:
|
||||
# **********************************************************************************
|
||||
# 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):
|
||||
return self.management_helper.add_to_audit_log(
|
||||
|
@ -17,6 +17,10 @@ class ServerPermsController:
|
||||
def 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
|
||||
def list_defined_permissions():
|
||||
permissions_list = PermissionsServers.get_permissions_list()
|
||||
@ -61,6 +65,22 @@ class ServerPermsController:
|
||||
def 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
|
||||
def set_permission(
|
||||
permission_mask, permission_tested: EnumPermissionsServer, value
|
||||
@ -82,6 +102,11 @@ class ServerPermsController:
|
||||
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
|
||||
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
|
||||
def get_authorized_servers_stats_from_roles(user_id):
|
||||
user_roles = HelperUsers.get_user_roles_id(user_id)
|
||||
|
53
app/classes/logging/log_formatter.py
Normal file
53
app/classes/logging/log_formatter.py
Normal 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)
|
236
app/classes/minecraft/bigbucket.py
Normal file
236
app/classes/minecraft/bigbucket.py
Normal file
@ -0,0 +1,236 @@
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import requests
|
||||
|
||||
from app.classes.controllers.servers_controller import ServersController
|
||||
from app.classes.models.server_permissions import PermissionsServers
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Temp type var until sjars restores generic fetchTypes0
|
||||
|
||||
|
||||
class BigBucket:
|
||||
def __init__(self, helper):
|
||||
self.helper = helper
|
||||
# remove any trailing slash from config.json
|
||||
# url since we add it on all the calls
|
||||
self.base_url = str(
|
||||
self.helper.get_setting("big_bucket_repo", "https://jars.arcadiatech.org")
|
||||
).rstrip("/")
|
||||
|
||||
def _read_cache(self) -> dict:
|
||||
cache_file = self.helper.big_bucket_cache
|
||||
cache = {}
|
||||
try:
|
||||
with open(cache_file, "r", encoding="utf-8") as f:
|
||||
cache = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read big_bucket cache file: {e}")
|
||||
|
||||
return cache
|
||||
|
||||
def get_bucket_data(self):
|
||||
data = self._read_cache()
|
||||
return data.get("categories")
|
||||
|
||||
def _check_bucket_alive(self) -> bool:
|
||||
logger.info("Checking Big Bucket status")
|
||||
|
||||
check_url = f"{self.base_url}/healthcheck"
|
||||
try:
|
||||
response = requests.get(check_url, timeout=2)
|
||||
response_json = response.json()
|
||||
if (
|
||||
response.status_code in [200, 201]
|
||||
and response_json.get("status") == "ok"
|
||||
):
|
||||
logger.info("Big bucket is alive and responding as expected")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to connect to big bucket due to error: {e}")
|
||||
return False
|
||||
|
||||
logger.error(
|
||||
"Big bucket manifest is not available as expected or unable to contact"
|
||||
)
|
||||
return False
|
||||
|
||||
def _get_big_bucket(self) -> dict:
|
||||
logger.debug("Calling for big bucket manifest.")
|
||||
try:
|
||||
response = requests.get(f"{self.base_url}/manifest.json", timeout=5)
|
||||
if response.status_code in [200, 201]:
|
||||
data = response.json()
|
||||
del data["manifest_version"]
|
||||
return data
|
||||
return {}
|
||||
except TimeoutError as e:
|
||||
logger.error(f"Unable to get jars from remote with error {e}")
|
||||
return {}
|
||||
|
||||
def _refresh_cache(self):
|
||||
"""
|
||||
Contains the shared logic for refreshing the cache.
|
||||
This method is called by both manual_refresh_cache and refresh_cache methods.
|
||||
"""
|
||||
if not self._check_bucket_alive():
|
||||
logger.error("big bucket API is not available.")
|
||||
return False
|
||||
|
||||
cache_data = {
|
||||
"last_refreshed": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"),
|
||||
"categories": self._get_big_bucket(),
|
||||
}
|
||||
try:
|
||||
with open(
|
||||
self.helper.big_bucket_cache, "w", encoding="utf-8"
|
||||
) as cache_file:
|
||||
json.dump(cache_data, cache_file, indent=4)
|
||||
logger.info("Cache file successfully refreshed manually.")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update cache file manually: {e}")
|
||||
|
||||
def manual_refresh_cache(self):
|
||||
"""
|
||||
Manually triggers the cache refresh process.
|
||||
"""
|
||||
logger.info("Manual bucket cache refresh initiated.")
|
||||
self._refresh_cache()
|
||||
logger.info("Manual refresh completed.")
|
||||
|
||||
def refresh_cache(self):
|
||||
"""
|
||||
Automatically trigger cache refresh process based age.
|
||||
|
||||
This method checks if the cache file is older than a specified number of days
|
||||
before deciding to refresh.
|
||||
"""
|
||||
cache_file_path = self.helper.big_bucket_cache
|
||||
|
||||
# Determine if the cache is old and needs refreshing
|
||||
cache_old = self.helper.is_file_older_than_x_days(cache_file_path)
|
||||
|
||||
# debug override
|
||||
# cache_old = True
|
||||
|
||||
if not self._check_bucket_alive():
|
||||
logger.error("big bucket API is not available.")
|
||||
return False
|
||||
|
||||
if not cache_old:
|
||||
logger.info("Cache file is not old enough to require automatic refresh.")
|
||||
return False
|
||||
|
||||
logger.info("Automatic cache refresh initiated due to old cache.")
|
||||
self._refresh_cache()
|
||||
|
||||
def get_fetch_url(self, jar, server, version) -> str:
|
||||
"""
|
||||
Constructs the URL for downloading a server JAR file based on the server type.
|
||||
Parameters:
|
||||
jar (str): The category of the JAR file to download.
|
||||
server (str): Server software name (e.g., "paper").
|
||||
version (str): Server version.
|
||||
|
||||
Returns:
|
||||
str or None: URL for downloading the JAR file, or None if URL cannot be
|
||||
constructed or an error occurs.
|
||||
"""
|
||||
try:
|
||||
# Read cache file for URL that is in a list of one item
|
||||
return self.get_bucket_data()[jar]["types"][server]["versions"][version][
|
||||
"url"
|
||||
][0]
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while constructing fetch URL: {e}")
|
||||
return None
|
||||
|
||||
def download_jar(self, jar, server, version, path, server_id):
|
||||
update_thread = threading.Thread(
|
||||
name=f"server_download-{server_id}-{server}-{version}",
|
||||
target=self.a_download_jar,
|
||||
daemon=True,
|
||||
args=(jar, server, version, path, server_id),
|
||||
)
|
||||
update_thread.start()
|
||||
|
||||
def a_download_jar(self, jar, server, version, path, server_id):
|
||||
"""
|
||||
Downloads a server JAR file and performs post-download actions including
|
||||
notifying users and setting import status.
|
||||
|
||||
This method waits for the server registration to complete, retrieves the
|
||||
download URL for the specified server JAR file.
|
||||
|
||||
Upon successful download, it either runs the installer for
|
||||
Forge servers or simply finishes the import process for other types. It
|
||||
notifies server users about the completion of the download.
|
||||
|
||||
Parameters:
|
||||
- jar (str): The category of the JAR file to download.
|
||||
- server (str): The type of server software (e.g., 'forge', 'paper').
|
||||
- version (str): The version of the server software.
|
||||
- path (str): The local filesystem path where the JAR file will be saved.
|
||||
- server_id (str): The unique identifier for the server being updated or
|
||||
imported, used for notifying users and setting the import status.
|
||||
|
||||
Returns:
|
||||
- bool: True if the JAR file was successfully downloaded and saved;
|
||||
False otherwise.
|
||||
|
||||
The method ensures that the server is properly registered before proceeding
|
||||
with the download and handles exceptions by logging errors and reverting
|
||||
the import status if necessary.
|
||||
"""
|
||||
# delaying download for server register to finish
|
||||
time.sleep(3)
|
||||
|
||||
fetch_url = self.get_fetch_url(jar, server, version)
|
||||
if not fetch_url:
|
||||
return False
|
||||
|
||||
server_users = PermissionsServers.get_server_user_list(server_id)
|
||||
|
||||
# Make sure the server is registered before updating its stats
|
||||
while True:
|
||||
try:
|
||||
ServersController.set_import(server_id)
|
||||
for user in server_users:
|
||||
WebSocketManager().broadcast_user(user, "send_start_reload", {})
|
||||
break
|
||||
except Exception as ex:
|
||||
logger.debug(f"Server not registered yet. Delaying download - {ex}")
|
||||
|
||||
# Initiate Download
|
||||
jar_dir = os.path.dirname(path)
|
||||
jar_name = os.path.basename(path)
|
||||
logger.info(fetch_url)
|
||||
success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name)
|
||||
|
||||
# Post-download actions
|
||||
if success:
|
||||
if server == "forge-installer":
|
||||
# If this is the newer Forge version, run the installer
|
||||
ServersController.finish_import(server_id, True)
|
||||
else:
|
||||
ServersController.finish_import(server_id)
|
||||
|
||||
# Notify users
|
||||
for user in server_users:
|
||||
WebSocketManager().broadcast_user(
|
||||
user, "notification", "Executable download finished"
|
||||
)
|
||||
time.sleep(3) # Delay for user notification
|
||||
WebSocketManager().broadcast_user(user, "send_start_reload", {})
|
||||
else:
|
||||
logger.error(f"Unable to save jar to {path} due to download failure.")
|
||||
ServersController.finish_import(server_id)
|
||||
|
||||
return success
|
@ -1,395 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import requests
|
||||
|
||||
from app.classes.controllers.servers_controller import ServersController
|
||||
from app.classes.models.server_permissions import PermissionsServers
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Temp type var until sjars restores generic fetchTypes0
|
||||
SERVERJARS_TYPES = ["modded", "proxies", "servers", "vanilla"]
|
||||
PAPERJARS = ["paper", "folia"]
|
||||
|
||||
|
||||
class ServerJars:
|
||||
def __init__(self, helper):
|
||||
self.helper = helper
|
||||
self.base_url = "https://api.serverjars.com"
|
||||
self.paper_base = "https://api.papermc.io"
|
||||
|
||||
@staticmethod
|
||||
def get_paper_jars():
|
||||
return PAPERJARS
|
||||
|
||||
def get_paper_versions(self, project):
|
||||
"""
|
||||
Retrieves a list of versions for a specified project from the PaperMC API.
|
||||
|
||||
Parameters:
|
||||
project (str): The project name to query for available versions.
|
||||
|
||||
Returns:
|
||||
list: A list of version strings available for the project. Returns an empty
|
||||
list if the API call fails or if no versions are found.
|
||||
|
||||
This function makes a GET request to the PaperMC API to fetch available project
|
||||
versions, The versions are returned in reverse order, with the most recent
|
||||
version first.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self.paper_base}/v2/projects/{project}/", timeout=2
|
||||
)
|
||||
response.raise_for_status()
|
||||
api_data = response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading project versions for {project}: {e}")
|
||||
return []
|
||||
|
||||
versions = api_data.get("versions", [])
|
||||
versions.reverse() # Ensure the most recent version comes first
|
||||
return versions
|
||||
|
||||
def get_paper_build(self, project, version):
|
||||
"""
|
||||
Fetches the latest build for a specified project and version from PaperMC API.
|
||||
|
||||
Parameters:
|
||||
project (str): Project name, typically a server software like 'paper'.
|
||||
version (str): Project version to fetch the build number for.
|
||||
|
||||
Returns:
|
||||
int or None: Latest build number if successful, None if not or on error.
|
||||
|
||||
This method attempts to query the PaperMC API for the latest build and
|
||||
handles exceptions by logging errors and returning None.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/",
|
||||
timeout=2,
|
||||
)
|
||||
response.raise_for_status()
|
||||
api_data = response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching build for {project} {version}: {e}")
|
||||
return None
|
||||
|
||||
builds = api_data.get("builds", [])
|
||||
return builds[-1] if builds else None
|
||||
|
||||
def _read_cache(self):
|
||||
cache_file = self.helper.serverjar_cache
|
||||
cache = {}
|
||||
try:
|
||||
with open(cache_file, "r", encoding="utf-8") as f:
|
||||
cache = json.load(f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read serverjars.com cache file: {e}")
|
||||
|
||||
return cache
|
||||
|
||||
def get_serverjar_data(self):
|
||||
data = self._read_cache()
|
||||
return data.get("types")
|
||||
|
||||
def _check_sjars_api_alive(self):
|
||||
logger.info("Checking serverjars.com API status")
|
||||
|
||||
check_url = f"{self.base_url}"
|
||||
try:
|
||||
response = requests.get(check_url, timeout=2)
|
||||
response_json = response.json()
|
||||
|
||||
if (
|
||||
response.status_code in [200, 201]
|
||||
and response_json.get("status") == "success"
|
||||
and response_json.get("response", {}).get("status") == "ok"
|
||||
):
|
||||
logger.info("Serverjars.com API is alive and responding as expected")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to connect to serverjar.com API due to error: {e}")
|
||||
return False
|
||||
|
||||
logger.error(
|
||||
"Serverjars.com API is not responding as expected or unable to contact"
|
||||
)
|
||||
return False
|
||||
|
||||
def _fetch_projects_for_type(self, server_type):
|
||||
"""
|
||||
Fetches projects for a given server type from the ServerJars API.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self.base_url}/api/fetchTypes/{server_type}", timeout=5
|
||||
)
|
||||
response.raise_for_status() # Ensure HTTP errors are caught
|
||||
data = response.json()
|
||||
if data.get("status") == "success":
|
||||
return data["response"].get("servers", [])
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching projects for type {server_type}: {e}")
|
||||
return []
|
||||
|
||||
def _get_server_type_list(self):
|
||||
"""
|
||||
Builds the type structure with projects fetched for each type.
|
||||
"""
|
||||
type_structure = {}
|
||||
for server_type in SERVERJARS_TYPES:
|
||||
projects = self._fetch_projects_for_type(server_type)
|
||||
type_structure[server_type] = {project: [] for project in projects}
|
||||
return type_structure
|
||||
|
||||
def _get_jar_versions(self, server_type, project_name, max_ver=50):
|
||||
"""
|
||||
Grabs available versions for specified project
|
||||
|
||||
Args:
|
||||
server_type (str): Server Type Category (modded, servers, etc)
|
||||
project_name (str): Target project (paper, forge, magma, etc)
|
||||
max (int, optional): Max versions returned. Defaults to 50.
|
||||
|
||||
Returns:
|
||||
list: An array of versions
|
||||
"""
|
||||
url = f"{self.base_url}/api/fetchAll/{server_type}/{project_name}?max={max_ver}"
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
response.raise_for_status() # Ensure HTTP errors are caught
|
||||
data = response.json()
|
||||
logger.debug(f"Received data for {server_type}/{project_name}: {data}")
|
||||
|
||||
if data.get("status") == "success":
|
||||
versions = [
|
||||
item.get("version")
|
||||
for item in data.get("response", [])
|
||||
if "version" in item
|
||||
]
|
||||
versions.reverse() # Reverse so versions are newest -> oldest
|
||||
logger.debug(f"Versions extracted: {versions}")
|
||||
return versions
|
||||
except requests.RequestException as e:
|
||||
logger.error(
|
||||
f"Error fetching jar versions for {server_type}/{project_name}: {e}"
|
||||
)
|
||||
|
||||
return []
|
||||
|
||||
def _refresh_cache(self):
|
||||
"""
|
||||
Contains the shared logic for refreshing the cache.
|
||||
This method is called by both manual_refresh_cache and refresh_cache methods.
|
||||
"""
|
||||
now = datetime.now()
|
||||
cache_data = {
|
||||
"last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"),
|
||||
"types": self._get_server_type_list(),
|
||||
}
|
||||
|
||||
for server_type, projects in cache_data["types"].items():
|
||||
for project_name in projects:
|
||||
versions = self._get_jar_versions(server_type, project_name)
|
||||
cache_data["types"][server_type][project_name] = versions
|
||||
|
||||
for paper_project in PAPERJARS:
|
||||
cache_data["types"]["servers"][paper_project] = self.get_paper_versions(
|
||||
paper_project
|
||||
)
|
||||
|
||||
return cache_data
|
||||
|
||||
def manual_refresh_cache(self):
|
||||
"""
|
||||
Manually triggers the cache refresh process.
|
||||
"""
|
||||
if not self._check_sjars_api_alive():
|
||||
logger.error("ServerJars API is not available.")
|
||||
return False
|
||||
|
||||
logger.info("Manual cache refresh requested.")
|
||||
cache_data = self._refresh_cache()
|
||||
|
||||
# Save the updated cache data
|
||||
try:
|
||||
with open(self.helper.serverjar_cache, "w", encoding="utf-8") as cache_file:
|
||||
json.dump(cache_data, cache_file, indent=4)
|
||||
logger.info("Cache file successfully refreshed manually.")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update cache file manually: {e}")
|
||||
|
||||
def refresh_cache(self):
|
||||
"""
|
||||
Automatically trigger cache refresh process based age.
|
||||
|
||||
This method checks if the cache file is older than a specified number of days
|
||||
before deciding to refresh.
|
||||
"""
|
||||
cache_file_path = self.helper.serverjar_cache
|
||||
|
||||
# Determine if the cache is old and needs refreshing
|
||||
cache_old = self.helper.is_file_older_than_x_days(cache_file_path)
|
||||
|
||||
# debug override
|
||||
# cache_old = True
|
||||
|
||||
if not self._check_sjars_api_alive():
|
||||
logger.error("ServerJars API is not available.")
|
||||
return False
|
||||
|
||||
if not cache_old:
|
||||
logger.info("Cache file is not old enough to require automatic refresh.")
|
||||
return False
|
||||
|
||||
logger.info("Automatic cache refresh initiated due to old cache.")
|
||||
cache_data = self._refresh_cache()
|
||||
|
||||
# Save the updated cache data
|
||||
try:
|
||||
with open(cache_file_path, "w", encoding="utf-8") as cache_file:
|
||||
json.dump(cache_data, cache_file, indent=4)
|
||||
logger.info("Cache file successfully refreshed automatically.")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update cache file automatically: {e}")
|
||||
|
||||
def get_fetch_url(self, jar, server, version):
|
||||
"""
|
||||
Constructs the URL for downloading a server JAR file based on the server type.
|
||||
|
||||
Supports two main types of server JAR sources:
|
||||
- ServerJars API for servers not in PAPERJARS.
|
||||
- Paper API for servers available through the Paper project.
|
||||
|
||||
Parameters:
|
||||
jar (str): Name of the JAR file.
|
||||
server (str): Server software name (e.g., "paper").
|
||||
version (str): Server version.
|
||||
|
||||
Returns:
|
||||
str or None: URL for downloading the JAR file, or None if URL cannot be
|
||||
constructed or an error occurs.
|
||||
"""
|
||||
try:
|
||||
# Check if the server type is not specifically handled by Paper.
|
||||
if server not in PAPERJARS:
|
||||
return f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
|
||||
|
||||
# For Paper servers, attempt to get the build for the specified version.
|
||||
paper_build_info = self.get_paper_build(server, version)
|
||||
if paper_build_info is None:
|
||||
# Log an error or handle the case where paper_build_info is None
|
||||
logger.error(
|
||||
"Error: Unable to get build information for server:"
|
||||
f" {server}, version: {version}"
|
||||
)
|
||||
return None
|
||||
|
||||
build = paper_build_info.get("build")
|
||||
if not build:
|
||||
# Log an error or handle the case where build is None or not found
|
||||
logger.error(
|
||||
f"Error: Build number not found for server:"
|
||||
f" {server}, version: {version}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Construct and return the URL for downloading the Paper server JAR.
|
||||
return (
|
||||
f"{self.paper_base}/v2/projects/{server}/versions/{version}/"
|
||||
f"builds/{build}/downloads/{server}-{version}-{build}.jar"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while constructing fetch URL: {e}")
|
||||
return None
|
||||
|
||||
def download_jar(self, jar, server, version, path, server_id):
|
||||
update_thread = threading.Thread(
|
||||
name=f"server_download-{server_id}-{server}-{version}",
|
||||
target=self.a_download_jar,
|
||||
daemon=True,
|
||||
args=(jar, server, version, path, server_id),
|
||||
)
|
||||
update_thread.start()
|
||||
|
||||
def a_download_jar(self, jar, server, version, path, server_id):
|
||||
"""
|
||||
Downloads a server JAR file and performs post-download actions including
|
||||
notifying users and setting import status.
|
||||
|
||||
This method waits for the server registration to complete, retrieves the
|
||||
download URL for the specified server JAR file.
|
||||
|
||||
Upon successful download, it either runs the installer for
|
||||
Forge servers or simply finishes the import process for other types. It
|
||||
notifies server users about the completion of the download.
|
||||
|
||||
Parameters:
|
||||
- jar (str): The name of the JAR file to download.
|
||||
- server (str): The type of server software (e.g., 'forge', 'paper').
|
||||
- version (str): The version of the server software.
|
||||
- path (str): The local filesystem path where the JAR file will be saved.
|
||||
- server_id (str): The unique identifier for the server being updated or
|
||||
imported, used for notifying users and setting the import status.
|
||||
|
||||
Returns:
|
||||
- bool: True if the JAR file was successfully downloaded and saved;
|
||||
False otherwise.
|
||||
|
||||
The method ensures that the server is properly registered before proceeding
|
||||
with the download and handles exceptions by logging errors and reverting
|
||||
the import status if necessary.
|
||||
"""
|
||||
# delaying download for server register to finish
|
||||
time.sleep(3)
|
||||
|
||||
fetch_url = self.get_fetch_url(jar, server, version)
|
||||
if not fetch_url:
|
||||
return False
|
||||
|
||||
server_users = PermissionsServers.get_server_user_list(server_id)
|
||||
|
||||
# Make sure the server is registered before updating its stats
|
||||
while True:
|
||||
try:
|
||||
ServersController.set_import(server_id)
|
||||
for user in server_users:
|
||||
WebSocketManager().broadcast_user(user, "send_start_reload", {})
|
||||
break
|
||||
except Exception as ex:
|
||||
logger.debug(f"Server not registered yet. Delaying download - {ex}")
|
||||
|
||||
# Initiate Download
|
||||
jar_dir = os.path.dirname(path)
|
||||
jar_name = os.path.basename(path)
|
||||
logger.info(fetch_url)
|
||||
success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name)
|
||||
|
||||
# Post-download actions
|
||||
if success:
|
||||
if server == "forge":
|
||||
# If this is the newer Forge version, run the installer
|
||||
ServersController.finish_import(server_id, True)
|
||||
else:
|
||||
ServersController.finish_import(server_id)
|
||||
|
||||
# Notify users
|
||||
for user in server_users:
|
||||
WebSocketManager().broadcast_user(
|
||||
user, "notification", "Executable download finished"
|
||||
)
|
||||
time.sleep(3) # Delay for user notification
|
||||
WebSocketManager().broadcast_user(user, "send_start_reload", {})
|
||||
else:
|
||||
logger.error(f"Unable to save jar to {path} due to download failure.")
|
||||
ServersController.finish_import(server_id)
|
||||
|
||||
return success
|
@ -187,7 +187,7 @@ class PermissionsCrafty:
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys):
|
||||
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()
|
||||
if user["superuser"]:
|
||||
# User is superuser but API key isn't
|
||||
|
@ -18,28 +18,10 @@ from app.classes.models.base_model import BaseModel
|
||||
from app.classes.models.users import HelperUsers
|
||||
from app.classes.models.servers import Servers
|
||||
from app.classes.models.server_permissions import PermissionsServers
|
||||
from app.classes.shared.main_models import DatabaseShortcuts
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# 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"
|
||||
auth_logger = logging.getLogger("audit_log")
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
@ -155,10 +137,6 @@ class HelpersManagement:
|
||||
# **********************************************************************************
|
||||
# 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):
|
||||
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
|
||||
@ -172,50 +150,28 @@ class HelpersManagement:
|
||||
WebSocketManager().broadcast_user(user, "notification", audit_msg)
|
||||
except Exception as e:
|
||||
logger.error(f"Error broadcasting to user {user} - {e}")
|
||||
|
||||
AuditLog.insert(
|
||||
{
|
||||
AuditLog.user_name: user_data["username"],
|
||||
AuditLog.user_id: user_id,
|
||||
AuditLog.server_id: server_id,
|
||||
AuditLog.log_msg: audit_msg,
|
||||
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
|
||||
auth_logger.info(
|
||||
str(log_msg),
|
||||
extra={
|
||||
"user_name": user_data["username"],
|
||||
"user_id": user_id,
|
||||
"server_id": server_id,
|
||||
"source_ip": source_ip,
|
||||
},
|
||||
)
|
||||
|
||||
def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
|
||||
AuditLog.insert(
|
||||
{
|
||||
AuditLog.user_name: user_name,
|
||||
AuditLog.user_id: user_id,
|
||||
AuditLog.server_id: server_id,
|
||||
AuditLog.log_msg: log_msg,
|
||||
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:
|
||||
# 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
|
||||
if isinstance(server_id, Servers) and server_id is not None:
|
||||
server_id = server_id.server_id
|
||||
auth_logger.info(
|
||||
str(log_msg),
|
||||
extra={
|
||||
"user_name": user_name,
|
||||
"user_id": user_id,
|
||||
"server_id": server_id,
|
||||
"source_ip": source_ip,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_crafty_row():
|
||||
|
@ -264,7 +264,7 @@ class PermissionsServers:
|
||||
@staticmethod
|
||||
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
|
||||
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()
|
||||
roles_list = HelperUsers.get_user_roles_id(user["user_id"])
|
||||
role_server = (
|
||||
|
@ -71,7 +71,7 @@ class ApiKeys(BaseModel):
|
||||
user_id = ForeignKeyField(Users, backref="api_token", index=True)
|
||||
server_permissions = CharField(default="00000000")
|
||||
crafty_permissions = CharField(default="000")
|
||||
superuser = BooleanField(default=False)
|
||||
full_access = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = "api_keys"
|
||||
@ -407,7 +407,7 @@ class HelperUsers:
|
||||
def add_user_api_key(
|
||||
name: str,
|
||||
user_id: str,
|
||||
superuser: bool = False,
|
||||
full_access: bool = False,
|
||||
server_permissions_mask: t.Optional[str] = None,
|
||||
crafty_permissions_mask: t.Optional[str] = None,
|
||||
):
|
||||
@ -425,7 +425,7 @@ class HelperUsers:
|
||||
if crafty_permissions_mask is not None
|
||||
else {}
|
||||
),
|
||||
ApiKeys.superuser: superuser,
|
||||
ApiKeys.full_access: full_access,
|
||||
}
|
||||
).execute()
|
||||
|
||||
|
@ -72,7 +72,7 @@ class Helpers:
|
||||
self.db_path = os.path.join(
|
||||
self.root_dir, "app", "config", "db", "crafty.sqlite"
|
||||
)
|
||||
self.serverjar_cache = os.path.join(self.config_dir, "serverjars.json")
|
||||
self.big_bucket_cache = os.path.join(self.config_dir, "bigbucket.json")
|
||||
self.credits_cache = os.path.join(self.config_dir, "credits.json")
|
||||
self.passhasher = PasswordHasher()
|
||||
self.exiting = False
|
||||
@ -516,6 +516,7 @@ class Helpers:
|
||||
"monitored_mounts": mounts,
|
||||
"dir_size_poll_freq_minutes": 5,
|
||||
"crafty_logs_delete_after_days": 0,
|
||||
"big_bucket_repo": "https://jars.arcadiatech.org",
|
||||
}
|
||||
|
||||
def get_all_settings(self):
|
||||
|
@ -32,7 +32,7 @@ from app.classes.shared.console import Console
|
||||
from app.classes.shared.helpers import Helpers
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
from app.classes.shared.import_helper import ImportHelpers
|
||||
from app.classes.minecraft.serverjars import ServerJars
|
||||
from app.classes.minecraft.bigbucket import BigBucket
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -43,7 +43,7 @@ class Controller:
|
||||
self.helper: Helpers = helper
|
||||
self.file_helper: FileHelpers = file_helper
|
||||
self.import_helper: ImportHelpers = import_helper
|
||||
self.server_jars: ServerJars = ServerJars(helper)
|
||||
self.big_bucket: BigBucket = BigBucket(helper)
|
||||
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
|
||||
self.roles_helper: HelperRoles = HelperRoles(database)
|
||||
self.servers_helper: HelperServers = HelperServers(database)
|
||||
@ -436,7 +436,7 @@ class Controller:
|
||||
if root_create_data["create_type"] == "download_jar":
|
||||
if Helpers.is_os_windows():
|
||||
# Let's check for and setup for install server commands
|
||||
if create_data["type"] == "forge":
|
||||
if create_data["type"] == "forge-installer":
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||
@ -449,7 +449,7 @@ class Controller:
|
||||
f'-jar "{server_file}" nogui'
|
||||
)
|
||||
else:
|
||||
if create_data["type"] == "forge":
|
||||
if create_data["type"] == "forge-installer":
|
||||
server_command = (
|
||||
f"java -Xms{Helpers.float_to_string(min_mem)}M "
|
||||
f"-Xmx{Helpers.float_to_string(max_mem)}M "
|
||||
@ -568,19 +568,16 @@ class Controller:
|
||||
if data["create_type"] == "minecraft_java":
|
||||
if root_create_data["create_type"] == "download_jar":
|
||||
# modded update urls from server jars will only update the installer
|
||||
if (
|
||||
create_data["category"] != "modded"
|
||||
and create_data["type"] not in ServerJars.get_paper_jars()
|
||||
):
|
||||
if create_data["type"] != "forge-installer":
|
||||
server_obj = self.servers.get_server_obj(new_server_id)
|
||||
url = (
|
||||
"https://api.serverjars.com/api/fetchJar/"
|
||||
f"{create_data['category']}"
|
||||
f"/{create_data['type']}/{create_data['version']}"
|
||||
url = self.big_bucket.get_fetch_url(
|
||||
create_data["category"],
|
||||
create_data["type"],
|
||||
create_data["version"],
|
||||
)
|
||||
server_obj.executable_update_url = url
|
||||
self.servers.update_server(server_obj)
|
||||
self.server_jars.download_jar(
|
||||
self.big_bucket.download_jar(
|
||||
create_data["category"],
|
||||
create_data["type"],
|
||||
create_data["version"],
|
||||
|
@ -690,7 +690,8 @@ class ServerInstance:
|
||||
try:
|
||||
# Getting the forge version from the executable command
|
||||
version = re.findall(
|
||||
r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
|
||||
r"forge-installer-([0-9\.]+)((?:)|"
|
||||
r"(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
|
||||
server_obj.execution_command,
|
||||
)
|
||||
version_param = version[0][0].split(".")
|
||||
|
@ -685,16 +685,16 @@ class TasksManager:
|
||||
id="stats",
|
||||
)
|
||||
|
||||
def serverjar_cache_refresher(self):
|
||||
logger.info("Refreshing serverjars.com cache on start")
|
||||
self.controller.server_jars.refresh_cache()
|
||||
def big_bucket_cache_refresher(self):
|
||||
logger.info("Refreshing big bucket cache on start")
|
||||
self.controller.big_bucket.refresh_cache()
|
||||
|
||||
logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
|
||||
logger.info("Scheduling big bucket cache refresh service every 12 hours")
|
||||
self.scheduler.add_job(
|
||||
self.controller.server_jars.refresh_cache,
|
||||
self.controller.big_bucket.refresh_cache,
|
||||
"interval",
|
||||
hours=12,
|
||||
id="serverjars",
|
||||
id="big_bucket",
|
||||
)
|
||||
|
||||
def realtime(self):
|
||||
|
@ -182,6 +182,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
t.List[str],
|
||||
bool,
|
||||
t.Dict[str, t.Any],
|
||||
str,
|
||||
]
|
||||
]:
|
||||
try:
|
||||
@ -190,9 +191,10 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
)
|
||||
|
||||
superuser = user["superuser"]
|
||||
server_permissions_api_mask = ""
|
||||
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()
|
||||
if superuser:
|
||||
authorized_servers = self.controller.servers.get_all_defined_servers()
|
||||
@ -214,6 +216,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
user["user_id"]
|
||||
)
|
||||
)
|
||||
|
||||
logger.debug(user["roles"])
|
||||
for r in user["roles"]:
|
||||
role = self.controller.roles.get_role(r)
|
||||
@ -234,6 +237,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
exec_user_role,
|
||||
superuser,
|
||||
user,
|
||||
server_permissions_api_mask,
|
||||
)
|
||||
logging.debug("Auth unsuccessful")
|
||||
auth_log.error(
|
||||
|
@ -168,7 +168,7 @@ class PanelHandler(BaseHandler):
|
||||
# Commented out because there is no server access control for API keys,
|
||||
# they just inherit from the host user
|
||||
# if api_key is not None:
|
||||
# superuser = superuser and api_key.superuser
|
||||
# superuser = superuser and api_key.full_access
|
||||
|
||||
if server_id is None:
|
||||
self.redirect("/panel/error?error=Invalid Server ID")
|
||||
@ -242,7 +242,7 @@ class PanelHandler(BaseHandler):
|
||||
api_key, _token_data, exec_user = self.current_user
|
||||
superuser = exec_user["superuser"]
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
superuser = superuser and api_key.full_access
|
||||
|
||||
if superuser: # TODO: Figure out a better solution
|
||||
defined_servers = self.controller.servers.list_defined_servers()
|
||||
@ -351,7 +351,7 @@ class PanelHandler(BaseHandler):
|
||||
"created": api_key.created,
|
||||
"server_permissions": api_key.server_permissions,
|
||||
"crafty_permissions": api_key.crafty_permissions,
|
||||
"superuser": api_key.superuser,
|
||||
"full_access": api_key.full_access,
|
||||
}
|
||||
if api_key is not None
|
||||
else None
|
||||
@ -1224,9 +1224,11 @@ class PanelHandler(BaseHandler):
|
||||
page_data["schedule"]["interval_type"] = schedule.interval_type
|
||||
if schedule.interval_type == "reaction":
|
||||
difficulty = "reaction"
|
||||
page_data["parent"] = self.controller.management.get_scheduled_task(
|
||||
schedule.parent
|
||||
)
|
||||
page_data["parent"] = None
|
||||
if schedule.parent:
|
||||
page_data["parent"] = self.controller.management.get_scheduled_task(
|
||||
schedule.parent
|
||||
)
|
||||
elif schedule.cron_string == "":
|
||||
difficulty = "basic"
|
||||
page_data["parent"] = None
|
||||
@ -1417,6 +1419,9 @@ class PanelHandler(BaseHandler):
|
||||
page_data["crafty_permissions_all"] = (
|
||||
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:
|
||||
self.redirect("/panel/error?error=Invalid User ID")
|
||||
@ -1564,8 +1569,6 @@ class PanelHandler(BaseHandler):
|
||||
template = "panel/panel_edit_role.html"
|
||||
|
||||
elif page == "activity_logs":
|
||||
page_data["audit_logs"] = self.controller.management.get_activity_log()
|
||||
|
||||
template = "panel/activity_logs.html"
|
||||
|
||||
elif page == "download_file":
|
||||
|
@ -48,7 +48,10 @@ class PublicHandler(BaseHandler):
|
||||
}
|
||||
|
||||
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
|
||||
template = "public/404.html"
|
||||
|
@ -26,6 +26,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
_user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
data = self.helper.get_announcements()
|
||||
@ -72,6 +73,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
_user,
|
||||
_,
|
||||
) = auth_data
|
||||
try:
|
||||
data = json.loads(self.request.body)
|
||||
|
@ -1,3 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
|
||||
@ -12,6 +14,7 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
@ -22,9 +25,17 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
|
||||
raise NotImplementedError
|
||||
|
||||
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(
|
||||
200,
|
||||
{"status": "ok", "data": self.controller.management.get_activity_log()},
|
||||
{"status": "ok", "data": rev_log_lines},
|
||||
)
|
||||
|
||||
if log_type == "session":
|
||||
|
@ -31,6 +31,7 @@ config_json_schema = {
|
||||
"monitored_mounts": {"type": "array"},
|
||||
"dir_size_poll_freq_minutes": {"type": "integer"},
|
||||
"crafty_logs_delete_after_days": {"type": "integer"},
|
||||
"big_bucket_repo": {"type": "string"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
@ -67,6 +68,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/roles?ids=true
|
||||
@ -93,13 +95,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
(
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
) = auth_data
|
||||
(_, _, _, superuser, user, _) = auth_data
|
||||
|
||||
if not superuser:
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
@ -149,6 +145,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/roles?ids=true
|
||||
@ -181,6 +178,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
if not superuser:
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
@ -24,6 +24,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/roles?ids=true
|
||||
@ -56,6 +57,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not auth_data:
|
||||
|
@ -12,16 +12,17 @@ class ApiCraftyJarCacheIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not auth_data[4]["superuser"]:
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
self.controller.server_jars.manual_refresh_cache()
|
||||
self.controller.big_bucket.manual_refresh_cache()
|
||||
self.finish_json(
|
||||
200,
|
||||
{
|
||||
"status": "ok",
|
||||
"data": self.controller.server_jars.get_serverjar_data(),
|
||||
"data": self.controller.big_bucket.get_bucket_data(),
|
||||
},
|
||||
)
|
||||
|
@ -75,6 +75,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/roles?ids=true
|
||||
@ -107,6 +108,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
|
@ -74,6 +74,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
@ -97,6 +98,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
@ -126,10 +128,19 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
role = self.controller.roles.get_role(role_id)
|
||||
if not superuser and user["user_id"] != role["manager"]:
|
||||
return self.finish_json(
|
||||
400,
|
||||
{
|
||||
"status": "error",
|
||||
"error": "NOT_AUTHORIZED",
|
||||
"error_data": "Not Authorized",
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
data = orjson.loads(self.request.body)
|
||||
|
@ -13,6 +13,7 @@ class ApiRolesRoleServersHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/roles/role/servers?ids=true
|
||||
|
@ -12,6 +12,7 @@ class ApiRolesRoleUsersHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
_,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if not superuser:
|
||||
|
@ -139,7 +139,7 @@ new_server_schema = {
|
||||
"category": {
|
||||
"title": "Jar Category",
|
||||
"type": "string",
|
||||
"examples": ["modded", "vanilla"],
|
||||
"examples": ["Mc_java_servers", "Mc_java_proxies"],
|
||||
},
|
||||
"properties": {
|
||||
"type": {
|
||||
@ -685,6 +685,7 @@ class ApiServersIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if EnumPermissionsCrafty.SERVER_CREATION not in exec_user_crafty_permissions:
|
||||
|
@ -18,13 +18,14 @@ class ApiServersServerActionHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.COMMANDS
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.COMMANDS not in server_permissions:
|
||||
# if the user doesn't have Commands permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -26,12 +26,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.BACKUP not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
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)
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.BACKUP not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
@ -89,12 +93,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.BACKUP not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -42,12 +42,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.BACKUP not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
self.finish_json(200, self.controller.management.get_backup_config(server_id))
|
||||
@ -83,13 +85,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.BACKUP not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -80,16 +80,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
|
||||
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
|
||||
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 (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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
|
||||
)
|
||||
EnumPermissionsServer.FILES not in server_permissions
|
||||
and EnumPermissionsServer.BACKUP not in server_permissions
|
||||
):
|
||||
# if the user doesn't have Files or Backup permission, return an error
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
@ -254,13 +255,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
@ -307,13 +309,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
@ -373,13 +376,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
@ -438,13 +442,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
@ -504,13 +509,14 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.FILES
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.FILES not in server_permissions:
|
||||
# if the user doesn't have Files permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
try:
|
||||
|
@ -102,13 +102,14 @@ class ApiServersServerIndexHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Config permission, return an error
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Config permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -30,13 +30,14 @@ class ApiServersServerLogsHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.LOGS
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.LOGS not in server_permissions:
|
||||
# if the user doesn't have Logs permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -16,13 +16,14 @@ class ApiServersServerStdinHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.COMMANDS
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.COMMANDS not in server_permissions:
|
||||
# if the user doesn't have Commands permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -78,13 +78,14 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler):
|
||||
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
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.SCHEDULE not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
data["server_id"] = server_id
|
||||
|
@ -54,12 +54,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.SCHEDULE not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
|
||||
@ -68,12 +70,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.SCHEDULE not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.SCHEDULE
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.SCHEDULE not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
|
@ -38,12 +38,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
data["server_id"] = server_id
|
||||
|
@ -39,12 +39,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
if (
|
||||
@ -66,12 +68,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
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 the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.CONFIG
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
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 EnumPermissionsServer.CONFIG not in server_permissions:
|
||||
# if the user doesn't have Schedule permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
webhook = self.controller.management.get_webhook_by_id(webhook_id)
|
||||
|
@ -21,6 +21,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
# GET /api/v2/users?ids=true
|
||||
@ -70,6 +71,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
|
||||
@ -149,11 +151,12 @@ class ApiUsersIndexHandler(BaseApiHandler):
|
||||
400, {"status": "error", "error": "INVALID_SUPERUSER_CREATE"}
|
||||
)
|
||||
|
||||
if len(roles) != 0 and not superuser:
|
||||
# HACK: This should check if the user has the roles or something
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
|
||||
)
|
||||
for role in roles:
|
||||
role = self.controller.roles.get_role(role)
|
||||
if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
|
||||
)
|
||||
|
||||
# TODO: do this in the most efficient way
|
||||
user_id = self.controller.users.add_user(
|
||||
|
@ -75,7 +75,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||
"name": key.name,
|
||||
"server_permissions": key.server_permissions,
|
||||
"crafty_permissions": key.crafty_permissions,
|
||||
"superuser": key.superuser,
|
||||
"full_access": key.full_access,
|
||||
}
|
||||
)
|
||||
self.finish_json(
|
||||
@ -99,7 +99,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||
"type": "string",
|
||||
"pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty
|
||||
},
|
||||
"superuser": {"type": "boolean"},
|
||||
"full_access": {"type": "boolean"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
@ -113,6 +113,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||
_,
|
||||
_superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
try:
|
||||
@ -163,7 +164,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||
key_id = self.controller.users.add_user_api_key(
|
||||
data["name"],
|
||||
user_id,
|
||||
data["superuser"],
|
||||
data["full_access"],
|
||||
data["server_permissions_mask"],
|
||||
data["crafty_permissions_mask"],
|
||||
)
|
||||
@ -188,6 +189,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
_user,
|
||||
_,
|
||||
) = auth_data
|
||||
if key_id:
|
||||
key = self.controller.users.get_user_api_key(key_id)
|
||||
|
@ -24,6 +24,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if user_id in ["@me", user["user_id"]]:
|
||||
@ -72,6 +73,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if (user_id in ["@me", user["user_id"]]) and self.helper.get_setting(
|
||||
@ -121,6 +123,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
||||
_,
|
||||
superuser,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
try:
|
||||
|
@ -27,6 +27,7 @@ class ApiUsersUserPermissionsHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if user_id in ["@me", user["user_id"]]:
|
||||
|
@ -17,6 +17,7 @@ class ApiUsersUserPublicHandler(BaseApiHandler):
|
||||
_,
|
||||
_,
|
||||
user,
|
||||
_,
|
||||
) = auth_data
|
||||
|
||||
if user_id == "@me":
|
||||
|
@ -30,7 +30,7 @@ class ServerHandler(BaseHandler):
|
||||
) = self.current_user
|
||||
superuser = exec_user["superuser"]
|
||||
if api_key is not None:
|
||||
superuser = superuser and api_key.superuser
|
||||
superuser = superuser and api_key.full_access
|
||||
|
||||
if superuser:
|
||||
defined_servers = self.controller.servers.list_defined_servers()
|
||||
@ -124,7 +124,7 @@ class ServerHandler(BaseHandler):
|
||||
"created": api_key.created,
|
||||
"server_permissions": api_key.server_permissions,
|
||||
"crafty_permissions": api_key.crafty_permissions,
|
||||
"superuser": api_key.superuser,
|
||||
"full_access": api_key.full_access,
|
||||
}
|
||||
if api_key is not None
|
||||
else None
|
||||
@ -146,12 +146,12 @@ class ServerHandler(BaseHandler):
|
||||
return
|
||||
page_data["server_api"] = False
|
||||
if page_data["online"]:
|
||||
page_data["server_api"] = self.helper.check_address_status(
|
||||
"https://api.serverjars.com"
|
||||
page_data["server_api"] = (
|
||||
self.controller.big_bucket._check_bucket_alive()
|
||||
)
|
||||
page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
|
||||
page_data["server_types"] = self.controller.big_bucket.get_bucket_data()
|
||||
page_data["js_server_types"] = json.dumps(
|
||||
self.controller.server_jars.get_serverjar_data()
|
||||
self.controller.big_bucket.get_bucket_data()
|
||||
)
|
||||
if page_data["server_types"] is None:
|
||||
page_data["server_types"] = []
|
||||
|
@ -42,7 +42,7 @@ class UploadHandler(BaseHandler):
|
||||
if self.upload_type == "server_import":
|
||||
superuser = exec_user["superuser"]
|
||||
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"]
|
||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
||||
|
||||
@ -133,7 +133,7 @@ class UploadHandler(BaseHandler):
|
||||
elif self.upload_type == "background":
|
||||
superuser = exec_user["superuser"]
|
||||
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"]
|
||||
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)
|
||||
superuser = exec_user["superuser"]
|
||||
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"]
|
||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
||||
|
||||
|
@ -14,6 +14,9 @@
|
||||
"auth": {
|
||||
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
|
||||
},
|
||||
"audit": {
|
||||
"()": "app.classes.logging.log_formatter.JsonFormatter"
|
||||
},
|
||||
"cmd_queue": {
|
||||
"format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s"
|
||||
}
|
||||
@ -70,6 +73,14 @@
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"audit_log_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "audit",
|
||||
"filename": "logs/audit.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
@ -108,6 +119,12 @@
|
||||
"cmd_queue_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
},
|
||||
"audit_log": {
|
||||
"level": "INFO",
|
||||
"handlers": [
|
||||
"audit_log_handler"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 3,
|
||||
"sub": 3
|
||||
"minor": 4,
|
||||
"sub": 1
|
||||
}
|
||||
|
@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 683.6 143.8" style="enable-background:new 0 0 683.6 143.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.85;fill:#FFFFFF;enable-background:new ;}
|
||||
.st1{opacity:0.85;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
.st3{fill:none;}
|
||||
.st4{fill:url(#SVGID_1_);}
|
||||
.st5{fill:url(#SVGID_00000137122815686618769650000009047437546445953421_);}
|
||||
.st6{fill:url(#SVGID_00000170963539203169094570000007184871682409824703_);}
|
||||
.st7{fill:url(#SVGID_00000169549353698428389090000007910489870824235905_);}
|
||||
.st8{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000029754379306852418700000008865188217784465572_);}
|
||||
</style>
|
||||
<path class="st0" d="M175.8,111.5h17.6v3.8h-13.2v8.9h12.1v3.7h-12.1v11.8h-4.4V111.5z"/>
|
||||
<path class="st0" d="M196.3,119.1h4.2v3.5h0.1c0.4-2.3,2.4-3.9,4.7-3.9c0.5,0,1,0.1,1.5,0.2v3.9c-0.6-0.2-1.3-0.3-1.9-0.2
|
||||
c-2.7,0-4.4,1.8-4.4,4.8v12.3h-4.2L196.3,119.1z"/>
|
||||
<path class="st0" d="M207.2,129.4L207.2,129.4c0-6.6,3.9-10.6,9.7-10.6s9.8,4,9.8,10.6l0,0c0,6.6-3.9,10.7-9.8,10.7
|
||||
S207.2,136,207.2,129.4z M222.4,129.4L222.4,129.4c0-4.5-2.2-7.1-5.5-7.1s-5.4,2.6-5.4,7.1l0,0c0,4.5,2.2,7.2,5.5,7.2
|
||||
S222.4,133.9,222.4,129.4L222.4,129.4z"/>
|
||||
<path class="st0" d="M229.6,119.1h4.2v3.2h0.1c1-2.3,3.2-3.7,5.7-3.6c2.6-0.2,5,1.5,5.7,4h0.1c1.1-2.5,3.6-4.1,6.4-4
|
||||
c4.1,0,6.7,2.7,6.7,6.8v14.1h-4.2v-13.1c0-2.7-1.4-4.2-3.9-4.2c-2.3,0-4.2,1.8-4.3,4.2c0,0.1,0,0.2,0,0.3v12.9H242v-13.4
|
||||
c0.2-2-1.3-3.8-3.3-3.9c-0.2,0-0.4,0-0.5,0c-2.4,0-4.3,2-4.3,4.3c0,0.1,0,0.2,0,0.3v12.7h-4.2L229.6,119.1z"/>
|
||||
<g id="Layer_2_00000138553854520646606810000012156271018779627156_" class="st1">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st2" d="M343.7,139.9c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5c2.1,0,4.2,0.5,6,1.5c1.8,1,3.3,2.4,4.3,4.1
|
||||
l-4.1,2.4c-0.6-1.1-1.5-1.9-2.5-2.5c-3.1-1.6-6.8-1.1-9.4,1.3c-1.5,1.5-2.2,3.6-2.1,5.7c-0.1,2.1,0.7,4.1,2.1,5.7
|
||||
c1.5,1.5,3.5,2.3,5.7,2.2c1.3,0,2.6-0.3,3.7-0.9c1.1-0.6,2-1.4,2.5-2.5l4.1,2.4c-1,1.7-2.5,3.2-4.3,4.1
|
||||
C347.8,139.4,345.8,139.9,343.7,139.9z"/>
|
||||
<path class="st2" d="M361.4,122.3v3c0.3-1,1.1-1.9,2-2.5c1-0.6,2.1-0.9,3.2-0.8v4.9c-1.3-0.2-2.6,0.1-3.6,0.8
|
||||
c-1.1,0.8-1.7,2.2-1.6,3.5v8.2H357v-17.2H361.4z"/>
|
||||
<path class="st2" d="M381.6,124.3v-2h4.4v17.2h-4.4v-2c-1.4,1.7-3.4,2.6-5.6,2.5c-2.2,0-4.4-0.9-5.9-2.6c-1.6-1.8-2.5-4.1-2.4-6.5
|
||||
c-0.1-2.4,0.8-4.7,2.4-6.4c1.5-1.7,3.6-2.7,5.9-2.7C378.1,121.7,380.2,122.6,381.6,124.3z M373.4,134.3c1.9,1.8,4.9,1.8,6.8,0
|
||||
c0.9-0.9,1.4-2.2,1.4-3.5c0.1-1.3-0.4-2.6-1.4-3.5c-1.9-1.8-4.9-1.8-6.8,0c-0.9,0.9-1.4,2.2-1.3,3.5
|
||||
C372,132.1,372.5,133.4,373.4,134.3z"/>
|
||||
<path class="st2" d="M399.2,115v4.2c-2.4-0.2-3.6,0.8-3.7,2.9v0.2h3.6v4.3h-3.6v12.9h-4.4v-12.9h-2.5v-4.2h2.5v-0.2
|
||||
c-0.2-2,0.6-4.1,2-5.5C394.5,115.3,396.6,114.8,399.2,115z"/>
|
||||
<path class="st2" d="M411.6,122.3v4.2h-3.9v7.1c0,0.5,0.1,1,0.5,1.3c0.4,0.3,0.8,0.5,1.3,0.5c0.7,0,1.4,0,2.1,0v4
|
||||
c-3,0.3-5.1,0.1-6.4-0.8s-1.9-2.5-1.9-4.9v-7.1h-3v-4.2h3v-3.5l4.4-1.3v4.8L411.6,122.3z"/>
|
||||
<path class="st2" d="M427.2,124.3v-2h4.4v17.2h-4.4v-2c-1.4,1.7-3.4,2.6-5.6,2.5c-2.2,0-4.4-0.9-5.9-2.6c-1.6-1.8-2.5-4.1-2.4-6.5
|
||||
c-0.1-2.4,0.8-4.7,2.4-6.4c1.5-1.7,3.6-2.7,5.9-2.7C423.8,121.7,425.9,122.6,427.2,124.3z M419.1,134.3c1.9,1.8,4.9,1.8,6.8,0
|
||||
c0.9-0.9,1.4-2.2,1.4-3.5c0-1.3-0.4-2.5-1.4-3.5c-1.9-1.8-4.9-1.8-6.8,0c-0.9,0.9-1.4,2.2-1.3,3.5
|
||||
C417.7,132.1,418.2,133.4,419.1,134.3L419.1,134.3z"/>
|
||||
<path class="st2" d="M440.1,122.3v3c0.4-1,1.1-1.9,2-2.5c1-0.6,2.1-0.9,3.2-0.8v4.9c-1.3-0.2-2.6,0.1-3.6,0.8
|
||||
c-1.1,0.8-1.7,2.2-1.6,3.5v8.2h-4.4v-17.2H440.1z"/>
|
||||
<path class="st2" d="M461.9,137.3c-3.6,3.6-9.3,3.6-12.9,0s-3.6-9.3,0-12.9l0,0c3.6-3.5,9.3-3.5,12.9,0.1c1.7,1.7,2.6,4,2.6,6.4
|
||||
C464.5,133.3,463.6,135.6,461.9,137.3z M452.1,134.3c1.9,1.8,4.8,1.8,6.7,0c1.8-1.9,1.8-4.9,0-6.8c-1.9-1.8-4.8-1.8-6.7,0
|
||||
C450.3,129.4,450.3,132.3,452.1,134.3L452.1,134.3z"/>
|
||||
<path class="st2" d="M320,137.6l-2.9-20.3c-0.4-2.7-2.7-4.7-5.5-4.7h-9c-0.3,0-0.5,0.2-0.7,0.4l-0.9,2H292l-0.9-2
|
||||
c-0.1-0.3-0.4-0.4-0.7-0.4h-9c-2.7,0-5.1,2-5.5,4.7l-2.9,20.3c-0.4,3,1.7,5.8,4.7,6.2c0,0,0,0,0,0l0,0c0.3,0,0.5,0.1,0.8,0.1h36
|
||||
c3,0,5.5-2.5,5.5-5.5l0,0C320,138.1,320,137.8,320,137.6z M287.1,130c-2.7,0-4.9-2.2-4.9-4.9c0-2.7,2.2-4.9,4.9-4.9
|
||||
c2.7,0,4.9,2.2,4.9,4.9c0,0,0,0,0,0l0,0C292,127.8,289.8,130,287.1,130z M296.5,138c-2.7,0-4.9-2.2-4.9-4.9h9.8
|
||||
C301.4,135.8,299.3,138,296.5,138L296.5,138L296.5,138z M305.9,130c-2.7,0-4.9-2.2-4.9-4.9c0-2.7,2.2-4.9,4.9-4.9
|
||||
c2.7,0,4.9,2.2,4.9,4.9c0,0,0,0,0,0l0,0C310.8,127.8,308.6,130,305.9,130L305.9,130z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st2" d="M133.1,19.2H9.7c-1.8,0-3.2-1.4-3.2-3.2V3.2C6.5,1.5,7.9,0,9.7,0h123.4c1.8,0,3.2,1.4,3.2,3.2V16
|
||||
C136.3,17.8,134.9,19.2,133.1,19.2"/>
|
||||
<path class="st2" d="M23.6,36.7c-3.4,0-6.7,1.6-8.8,4.3c-2.9,3.6-4.1,8.3-3.2,12.8l9.2,51.9c1.2,6.6,6.2,11.4,12.1,11.4H110
|
||||
c5.8,0,10.9-4.8,12.1-11.4l9.2-51.9c0.8-4.5-0.4-9.2-3.3-12.8c-2.1-2.7-5.4-4.3-8.8-4.3H23.6z M110,128.3H32.8
|
||||
c-11.3,0-21-8.7-23.1-20.7L0.5,55.8c-1.5-7.8,0.6-15.9,5.7-22c4.3-5.2,10.7-8.3,17.4-8.3h95.6c6.8,0.1,13.1,3.1,17.4,8.3
|
||||
c5.1,6.1,7.2,14.2,5.7,22l-9.2,51.9C130.9,119.7,121.2,128.4,110,128.3"/>
|
||||
<path class="st2" d="M120.8,23.8v-2.2c2,0,3.5-1.6,3.5-3.6c0-1.8-1.5-3.4-3.3-3.5H21.6c-2,0.1-3.5,1.8-3.4,3.7
|
||||
c0.1,1.8,1.5,3.3,3.4,3.4v2.2c-3.2-0.1-5.7-2.8-5.6-6c0.1-3,2.5-5.4,5.6-5.6h99.2c3.2-0.1,5.9,2.4,6,5.6s-2.4,5.9-5.6,6
|
||||
C121.1,23.8,121,23.8,120.8,23.8"/>
|
||||
<path class="st2" d="M120.8,33.1H21.6c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8v2.2c-2,0.1-3.5,1.8-3.4,3.7
|
||||
c0.1,1.8,1.5,3.3,3.4,3.4h99.2c2,0.1,3.7-1.3,3.8-3.3c0.1-2-1.3-3.7-3.3-3.8c-0.1,0-0.2,0-0.3,0h-0.2v-2.2c3.2-0.1,5.9,2.4,6,5.6
|
||||
s-2.4,5.9-5.6,6C121.1,33.1,121,33.1,120.8,33.1"/>
|
||||
<path class="st2" d="M21.6,21.5l36.1,1.1l-36.1,1.1V21.5z"/>
|
||||
<path class="st2" d="M125.5,23.8l-45.1-1.1l45.1-1.1V23.8z"/>
|
||||
<rect x="-2.5" y="-1.1" class="st3" width="571.3" height="131.4"/>
|
||||
<path class="st2" d="M163.8,91.7l7.3-10.9c5.8,5.5,14.3,9.3,22.3,9.3c7.1,0,13.1-3.3,13.1-8.3c0-6-8.1-7.9-15.4-9.6
|
||||
c-13.7-3.2-24.8-9.8-24.8-22.3c0-12.7,11.1-21,27.1-21c10.7,0,19.4,3.7,24.7,8.9l-6.6,10.8c-4-3.9-11.2-6.9-18.3-6.9
|
||||
s-12.2,3.2-12.2,7.7c0,5.5,7.4,7.9,14.1,9.3s26.2,6.2,26.2,22.5c0,12.8-12.2,21.6-27.8,21.6C182.6,102.8,171.1,98.4,163.8,91.7z"/>
|
||||
<path class="st2" d="M281.7,80.1h-40.9c1.9,6.6,7.5,10.9,15.1,10.9c5.6,0.1,10.9-2.3,14.5-6.5l9,7.9c-5.5,6.5-14,10.5-23.9,10.5
|
||||
c-16.8,0-29.3-12-29.3-27.8c0-15.6,12.1-27.4,28-27.4S282,59.4,282,75.3C282,76.9,281.9,78.5,281.7,80.1z M240.8,70.3h26.9
|
||||
c-1.7-6.6-6.9-10.9-13.4-10.9C247.7,59.4,242.5,63.8,240.8,70.3L240.8,70.3z"/>
|
||||
<path class="st2" d="M321.3,48v13.9h-2.3c-9.6,0-15.2,5.7-15.2,14.7v25h-13.4V48.9h13.5v6.8c3.6-4.8,9.2-7.7,15.2-7.7L321.3,48z"/>
|
||||
<path class="st2" d="M381.9,48.9L360,101.6h-13.9l-21.9-52.8h15.3l13.8,35.9L367,48.9H381.9z"/>
|
||||
<path class="st2" d="M437.1,80.1h-40.9c1.9,6.6,7.5,10.9,15.1,10.9c5.6,0.1,10.9-2.3,14.5-6.5l9,7.9c-5.5,6.5-14,10.5-23.9,10.5
|
||||
c-16.8,0-29.3-12-29.3-27.8c0-15.6,12.1-27.4,28-27.4s27.7,11.8,27.7,27.7C437.4,76.9,437.3,78.5,437.1,80.1z M396.1,70.3H423
|
||||
c-1.7-6.6-6.9-10.9-13.4-10.9S397.7,63.8,396.1,70.3L396.1,70.3z"/>
|
||||
<path class="st2" d="M476.7,48v13.9h-2.2c-9.6,0-15.2,5.7-15.2,14.7v25h-13.5V48.9h13.5v6.8c3.6-4.8,9.2-7.7,15.2-7.7L476.7,48z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="870.0443" y1="434.2369" x2="907.1767" y2="465.2789" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
|
||||
<stop offset="0" style="stop-color:#FEAF6F"/>
|
||||
<stop offset="1" style="stop-color:#FD5E83"/>
|
||||
</linearGradient>
|
||||
<path class="st4" d="M492.5,100.6V87c3.2,1.4,6.6,2.1,10,2.2c7.3,0,11.8-3.9,11.8-10.9v-48h14.3V79c0,15-9.8,23.9-24.5,23.9
|
||||
C500,102.9,496.1,102.1,492.5,100.6z"/>
|
||||
<linearGradient id="SVGID_00000162328622213414588160000008200821717462734513_" gradientUnits="userSpaceOnUse" x1="920.7661" y1="434.5518" x2="972.3098" y2="477.6348" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
|
||||
<stop offset="0" style="stop-color:#FEAF6F"/>
|
||||
<stop offset="1" style="stop-color:#FD5E83"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000162328622213414588160000008200821717462734513_);" d="M593.2,48.9v52.8h-13.5v-6.3
|
||||
c-4.4,4.9-10.6,7.6-17.2,7.5c-14.7,0-25.8-11.9-25.8-27.6s11.1-27.6,25.8-27.6c6.5-0.1,12.8,2.7,17.2,7.5v-6.3L593.2,48.9z
|
||||
M579.8,75.2c0-8-6.6-14.5-14.6-14.5c-8,0-14.5,6.6-14.5,14.6c0,8,6.5,14.4,14.5,14.5c7.9,0.2,14.4-6,14.6-13.9
|
||||
C579.8,75.7,579.8,75.5,579.8,75.2z"/>
|
||||
<linearGradient id="SVGID_00000026849485640012965730000014957007722205225107_" gradientUnits="userSpaceOnUse" x1="973.2171" y1="437.9167" x2="1007.0711" y2="466.2133" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
|
||||
<stop offset="0" style="stop-color:#FEAF6F"/>
|
||||
<stop offset="1" style="stop-color:#FD5E83"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000026849485640012965730000014957007722205225107_);" d="M635.9,48v13.9h-2.3
|
||||
c-9.6,0-15.2,5.7-15.2,14.7v25H605V48.9h13.4v6.8c3.6-4.8,9.2-7.7,15.2-7.7L635.9,48z"/>
|
||||
<linearGradient id="SVGID_00000011000279650532451330000005619277557075874698_" gradientUnits="userSpaceOnUse" x1="1015.3561" y1="439.477" x2="1056.9301" y2="474.2302" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
|
||||
<stop offset="0" style="stop-color:#FEAF6F"/>
|
||||
<stop offset="1" style="stop-color:#FD5E83"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000011000279650532451330000005619277557075874698_);" d="M638.7,94.8l6.5-8.9
|
||||
c4.2,3.8,9.7,5.9,15.4,5.9c5.4,0,9.3-1.8,9.3-5c0-3.5-4.6-4.8-10.3-6.1c-8.4-1.9-19.2-4.5-19.2-16.5c0-11.2,9.8-16.7,21.5-16.7
|
||||
c7.4-0.1,14.6,2.3,20.5,6.9l-6.5,9c-3.9-3.1-8.7-4.8-13.7-4.9c-4.6,0-8.3,1.5-8.3,4.5c0,3.5,4.4,4.7,10.3,5.9
|
||||
c8.4,1.9,19.2,4.5,19.2,16.4c0,11.2-9.9,17.3-22.6,17.3C652.9,102.9,644.9,100.1,638.7,94.8z"/>
|
||||
<linearGradient id="SVGID_00000176732902084481618460000012775063734620060048_" gradientUnits="userSpaceOnUse" x1="408.7259" y1="431.5905" x2="485.4144" y2="495.6844" gradientTransform="matrix(1 0 0 1 -374.6 -381.3801)">
|
||||
<stop offset="0" style="stop-color:#FEAF6F"/>
|
||||
<stop offset="1" style="stop-color:#FD5E83"/>
|
||||
</linearGradient>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_00000176732902084481618460000012775063734620060048_);" d="
|
||||
M124.5,62c-12.7,0.9-27,5.5-35.7,12.3c-38.7,30.3-69.2-6.6-69.3-6.6l6.8,36.8c0.8,4.3,4.6,7.5,9,7.5l73,0.2c4.5,0,8.3-3.2,9.1-7.6
|
||||
L124.5,62z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 10 KiB |
@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 142.71 128.36"><defs><style>.cls-1{fill:#fff;}.cls-2{fill-rule:evenodd;fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="408.73" y1="431.59" x2="485.41" y2="495.68" gradientTransform="translate(-374.6 -381.38)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#feaf6f"/><stop offset="1" stop-color="#fd5e83"/></linearGradient></defs><path class="cls-1" d="M133.09,19.17H9.67A3.24,3.24,0,0,1,6.46,16V3.24A3.24,3.24,0,0,1,9.7,0H133.09a3.25,3.25,0,0,1,3.25,3.24V16a3.25,3.25,0,0,1-3.25,3.24"/><path class="cls-1" d="M23.61,36.67A11.41,11.41,0,0,0,14.8,41a15.79,15.79,0,0,0-3.25,12.8l9.18,51.92c1.17,6.62,6.25,11.42,12.06,11.42H110c5.82,0,10.89-4.8,12.06-11.42l9.18-51.91A15.86,15.86,0,0,0,128,41a11.5,11.5,0,0,0-8.82-4.33ZM110,128.35H32.8c-11.27,0-21-8.7-23.12-20.69L.46,55.75a26.72,26.72,0,0,1,5.71-22,22.77,22.77,0,0,1,17.41-8.34h95.56a22.8,22.8,0,0,1,17.41,8.34,26.79,26.79,0,0,1,5.71,22l-9.19,51.91c-2.12,12-11.84,20.7-23.12,20.7"/><path class="cls-1" d="M120.8,23.76V21.51A3.56,3.56,0,0,0,121,14.4H21.59a3.56,3.56,0,0,0,0,7.11v2.25a5.81,5.81,0,0,1,0-11.61H120.8a5.81,5.81,0,0,1,.48,11.61h-.48"/><path class="cls-1" d="M120.8,33.11H21.59a5.8,5.8,0,0,1,0-11.6v2.24a3.56,3.56,0,0,0,0,7.11H120.8a3.56,3.56,0,0,0,.52-7.1h-.52V21.51a5.81,5.81,0,0,1,.48,11.61,3.84,3.84,0,0,1-.48,0"/><path class="cls-1" d="M21.59,21.51l36.13,1.13L21.59,23.76Z"/><path class="cls-1" d="M125.46,23.76,80.35,22.64l45.11-1.13Z"/><path class="cls-2" d="M124.46,62c-12.72.93-27,5.55-35.7,12.34-38.69,30.34-69.25-6.6-69.28-6.58l6.75,36.83a9.16,9.16,0,0,0,9,7.52l73,.16a9.17,9.17,0,0,0,9.06-7.64Z"/></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -36,25 +36,21 @@
|
||||
<table class="table table-hover" id="audit_table" style="overflow: scroll;" width="100%">
|
||||
<thead>
|
||||
<tr class="rounded">
|
||||
<td>Username</td>
|
||||
<td>Time</td>
|
||||
<td>Action</td>
|
||||
<td>Server ID</td>
|
||||
<td>IP</td>
|
||||
<th>Time</th>
|
||||
<th>Username</th>
|
||||
<th>Action</th>
|
||||
<th>Server ID</th>
|
||||
<th>IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data['audit_logs'] %}
|
||||
<tr>
|
||||
<td>{{ row['user_name'] }}</td>
|
||||
<td>
|
||||
{{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }}
|
||||
<td colspan="5" id="image-div" class="text-center"> <!-- Center image within table -->
|
||||
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
|
||||
alt="Crafty Logo, Crafty is loading" width="20%"><br><br>{{ translate('datatables',
|
||||
'loadingRecords', data['lang'])}}
|
||||
</td>
|
||||
<td>{{ row['log_msg'] }}</td>
|
||||
<td>{{ row['server_id'] }}</td>
|
||||
<td>{{ row['source_ip'] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -79,17 +75,6 @@
|
||||
{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('ready for JS!')
|
||||
$('#audit_table').DataTable({
|
||||
'order': [1, 'desc']
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
@ -112,6 +97,74 @@
|
||||
$('.too_small').popover("hide");
|
||||
} // 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>
|
||||
|
||||
{% end %}
|
@ -20,7 +20,8 @@
|
||||
data-internet="{{ translate('startup', 'internet', data['lang']) }}"
|
||||
data-tasks="{{ translate('startup', 'tasks', data['lang']) }}"
|
||||
data-internals="{{ translate('startup', 'internals', data['lang']) }}"
|
||||
data-almost="{{ translate('startup', 'almost', data['lang']) }}">
|
||||
data-almost="{{ translate('startup', 'almost', data['lang']) }}"
|
||||
data-cache="{{ translate('startup', 'cache', data['lang'])}}">
|
||||
{{ translate('startup', 'starting', data['lang']) }}</h2>
|
||||
</div>
|
||||
|
||||
|
@ -58,7 +58,7 @@
|
||||
<!--<th>ID</th>-->
|
||||
<th>{{ translate('apiKeys', 'name', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'created', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'superUser', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'fullAccess', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'perms', data['lang']) }}</th>
|
||||
<th>{{ translate('apiKeys', 'buttons', data['lang']) }}</th>
|
||||
</tr>
|
||||
@ -70,7 +70,7 @@
|
||||
<td>{{ apikey.name }}</td>
|
||||
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
|
||||
<td>
|
||||
{% if apikey.superuser %}
|
||||
{% if apikey.full_access %}
|
||||
<span class="text-success">
|
||||
<i class="fas fa-check-square"></i> {{
|
||||
translate('apiKeys', 'yes', data['lang']) }}
|
||||
@ -148,9 +148,15 @@
|
||||
}}</label>
|
||||
</td>
|
||||
<td>
|
||||
{% if permission in data['user_crafty_permissions'] %}
|
||||
<input type="checkbox" class="crafty_perm"
|
||||
id="permission_{{ permission.name }}"
|
||||
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>
|
||||
</tr>
|
||||
{% end %}
|
||||
@ -158,8 +164,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<label for="superuser">Superuser</label>
|
||||
<input type="checkbox" class="" id="superuser" name="superuser" value="1">
|
||||
<label for="full_access">{{translate('apiKeys', 'fullAccess', data['lang'])}}</label>
|
||||
<input type="checkbox" class="" id="full_access" name="full_access" value="1">
|
||||
|
||||
<br />
|
||||
|
||||
@ -240,7 +246,7 @@
|
||||
"name": formDataObject.name,
|
||||
"server_permissions_mask": server_permissions,
|
||||
"crafty_permissions_mask": crafty_permissions,
|
||||
"superuser": $("#superuser").prop('checked'),
|
||||
"full_access": $("#full_access").prop('checked'),
|
||||
});
|
||||
console.log(formDataJsonString);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<!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>
|
||||
<!-- Required meta tags -->
|
||||
@ -60,6 +61,11 @@
|
||||
<b>{{ translate('error', 'hereIsTheError', data['lang']) }}: {{data['error']}}</b><br /><br />
|
||||
That's all the help I can give you - Godspeed
|
||||
<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',
|
||||
'contact', data['lang']) }}</a>
|
||||
</p>
|
||||
|
@ -171,7 +171,6 @@
|
||||
|
||||
//Create an object from the form data entries
|
||||
let formDataObject = Object.fromEntries(formData.entries());
|
||||
console.log(formDataObject)
|
||||
let res = await fetch(`/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -20,11 +20,6 @@
|
||||
<div class="col-sm-6 grid-margin stretch-card">
|
||||
<div class="card" id="creation_wizard">
|
||||
<div class="card-body">
|
||||
{% if data["server_api"] and data["online"] %}
|
||||
<a href="https://serverjars.com/" target="_blank" alt="serverjars icon"><img
|
||||
src="../../static/assets/images/serverjars/ICON.svg"
|
||||
style="float: right; width: 40px; position: relative;"></a>
|
||||
{% end %}
|
||||
|
||||
<h4>{{ translate('serverWizard', 'newServer', data['lang']) }}</h4>
|
||||
<br />
|
||||
@ -67,16 +62,19 @@
|
||||
{% end %}
|
||||
{% raw xsrf_form_html() %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang'])
|
||||
}}</label>
|
||||
<label for="server_jar">{{ translate('serverWizard', 'serverType', data['lang']) }}</label>
|
||||
<div class="input-group">
|
||||
<select required class="form-control form-control-lg select-css" id="server_jar"
|
||||
name="server_jar" onchange="serverJarChange(this)">
|
||||
<option value="None">{{ translate('serverWizard', 'selectType', data['lang']) }}</option>
|
||||
<select required class="form-control form-control-lg select-css" id="server_jar" name="type"
|
||||
onchange="serverJarChange(this)">
|
||||
<option value="">{{ translate('serverWizard', 'selectServer', data['lang']) }}</option>
|
||||
{% for s in data['server_types'] %}
|
||||
<option value="{{ s }}">{{ s.capitalize() }}</option>
|
||||
{% if data['server_types'][s].get("enabled", False) %}
|
||||
<option value="{{ s }}">{{ data["server_types"][s].get("friendly_name", s).capitalize() }}
|
||||
{% end %}
|
||||
</option>
|
||||
{% end %}
|
||||
</select>
|
||||
{% if data['super_user'] %}
|
||||
@ -108,7 +106,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<span data-html="true" class="version-hint text-center"
|
||||
data-content="⚠️ {{ translate('serverWizard', 'unsupported', data['lang']) }} ⚠️" , data-placement="right"></span>
|
||||
data-content="⚠️ {{ translate('serverWizard', 'unsupported', data['lang']) }} ⚠️" ,
|
||||
data-placement="right"></span>
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
|
||||
@ -191,10 +190,12 @@
|
||||
{% if not data["server_api"] and data["online"] %}
|
||||
<div class="api-alert" style="position: absolute; top: -5px; z-index: 100; opacity: .99;">
|
||||
<p style="color: white !important;"><i class="fas fa-exclamation-triangle" style="color: red;"></i> {{
|
||||
translate('error', 'serverJars1', data['lang']) }}<a style="color: red;" ;
|
||||
href="https://status.craftycontrol.com/status/craftycontrol" target="_blank" rel="noopener"> {{ translate('error',
|
||||
translate('error', 'bigBucket1', data['lang']) }}<a style="color: red;" ;
|
||||
href="https://status.craftycontrol.com/status/craftycontrol" target="_blank" rel="noopener"> {{
|
||||
translate('error',
|
||||
'craftyStatus', data['lang']) }}</a>
|
||||
{{ translate('error', 'serverJars2', data['lang']) }}</p>
|
||||
{{ translate('error', 'bigBucket2', data['lang']) }}</br></br><small>{{ translate('error', 'selfHost',
|
||||
data['lang'])}}</small></p>
|
||||
</div>
|
||||
{% end %}
|
||||
{% if not data["online"] %}
|
||||
@ -827,7 +828,7 @@
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||
closeButton: false
|
||||
});
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
getDirView();
|
||||
}, 2000);
|
||||
} else {
|
||||
@ -845,9 +846,9 @@
|
||||
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
|
||||
closeButton: false
|
||||
});
|
||||
setTimeout(function(){
|
||||
getDirView();
|
||||
}, 2000);
|
||||
setTimeout(function () {
|
||||
getDirView();
|
||||
}, 2000);
|
||||
});
|
||||
var upload = false;
|
||||
var file;
|
||||
@ -1137,7 +1138,7 @@
|
||||
function wait_msg(importing) {
|
||||
bootbox.alert({
|
||||
title: importing ? '{% raw translate("serverWizard", "importing", data["lang"]) %}' : '{% raw translate("serverWizard", "downloading", data["lang"]) %}',
|
||||
message: importing ? '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}' : '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}<br><br><a href="https://serverjars.com" target="_blank" style="text-align: center;"><img src="../../static/assets/images/serverjars/FULL-WHITE.svg" alt="Powered by serverjars.com" width="40%"></a>',
|
||||
message: importing ? '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}' : '<i class="fas fa-cloud-download"></i> {% raw translate("serverWizard", "bePatient", data["lang"]) %}',
|
||||
});
|
||||
}
|
||||
|
||||
@ -1197,33 +1198,28 @@
|
||||
*/
|
||||
function serverTypeChange(selectObj) {
|
||||
// get the index of the selected option
|
||||
let idx = document.getElementById('server_type').selectedIndex;
|
||||
let idx = document.getElementById('server_type').value;
|
||||
let idx_list = idx.split("|");
|
||||
// get the value of the selected option
|
||||
let cSelect = document.getElementById("server");
|
||||
let which = {};
|
||||
try {
|
||||
which = document.getElementById('server_type').options[idx].value;
|
||||
versions = Object.keys(serverTypesLists[idx_list[0]]["types"][idx_list[1]]["versions"]);
|
||||
} catch {
|
||||
while (cSelect.options.length > 0) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let server_type = which.split('|')[0];
|
||||
let server = which.split('|')[1];
|
||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||
let cList = serverTypesLists[server_type];
|
||||
// get the country select element via its known id
|
||||
cSelect = document.getElementById("server");
|
||||
// remove the current options from the country select
|
||||
while (cSelect.options.length > 0) {
|
||||
cSelect.remove(0);
|
||||
}
|
||||
let newOption;
|
||||
|
||||
$("#server option").each(function () {
|
||||
$(this).remove()
|
||||
})
|
||||
|
||||
// create new options ordered by ascending
|
||||
cList[server].forEach(type => {
|
||||
versions.forEach(type => {
|
||||
newOption = document.createElement("option");
|
||||
newOption.value = which + "|" + type; // assumes option string and value are the same
|
||||
newOption.value = idx + "|" + type; // assumes option string and value are the same
|
||||
newOption.text = type;
|
||||
// add the new option
|
||||
try {
|
||||
@ -1234,17 +1230,18 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
$("#server").change(function (){
|
||||
|
||||
$("#server").change(function () {
|
||||
let selected_version = $("#server :selected").text().split(".");
|
||||
if(parseInt(selected_version[0]) === 1 && parseInt(selected_version[1]) < 8 ){
|
||||
if (parseInt(selected_version[0]) === 1 && parseInt(selected_version[1]) < 8) {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
if ($(window).width() < 1000) {
|
||||
$('.version-hint').attr("data-placement", "top")
|
||||
if ($(window).width() < 1000) {
|
||||
$('.version-hint').attr("data-placement", "top")
|
||||
} else {
|
||||
$('.version-hint').attr("data-placement", "right")
|
||||
}
|
||||
$('.version-hint').popover("show");
|
||||
} else {
|
||||
$('.version-hint').attr("data-placement", "right")
|
||||
}
|
||||
$('.version-hint').popover("show");
|
||||
}else{
|
||||
$('.version-hint').popover("hide");
|
||||
}
|
||||
});
|
||||
@ -1253,6 +1250,15 @@
|
||||
const type_select = document.getElementById('server_jar')
|
||||
const tidx = type_select.selectedIndex;
|
||||
const val = type_select.options[tidx].value;
|
||||
if (!val) {
|
||||
$("#server_type option").each(function () {
|
||||
$(this).remove()
|
||||
})
|
||||
$("#server option").each(function () {
|
||||
$(this).remove()
|
||||
})
|
||||
return;
|
||||
}
|
||||
let jcSelect = {};
|
||||
if (val == 'None') {
|
||||
jcSelect = document.getElementById("server_type");
|
||||
@ -1267,7 +1273,7 @@
|
||||
// get the value of the selected option
|
||||
let jwhich = selectObj.options[jidx].value;
|
||||
// use the selected option value to retrieve the list of items from the serverTypesLists array
|
||||
let jcList = Object.keys(serverTypesLists[jwhich]);
|
||||
let jcList = Object.keys(serverTypesLists[jwhich]["types"]);
|
||||
// get the country select element via its known id
|
||||
jcSelect = document.getElementById("server_type");
|
||||
// remove the current options from the country select
|
||||
|
@ -6,7 +6,6 @@ import logging
|
||||
from app.classes.shared.console import Console
|
||||
from app.classes.shared.migration import Migrator, MigrateHistory
|
||||
from app.classes.models.management import (
|
||||
AuditLog,
|
||||
Webhooks,
|
||||
Schedules,
|
||||
Backups,
|
||||
@ -61,17 +60,6 @@ def migrate(migrator: Migrator, database, **kwargs):
|
||||
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
|
||||
migrator.alter_column_type(
|
||||
Webhooks,
|
||||
@ -109,13 +97,6 @@ def rollback(migrator: Migrator, database, **kwargs):
|
||||
peewee.AutoField(),
|
||||
)
|
||||
|
||||
# Changes on Audit Log Table
|
||||
migrator.alter_column_type(
|
||||
AuditLog,
|
||||
"server_id",
|
||||
peewee.IntegerField(default=None, index=True),
|
||||
)
|
||||
|
||||
# Changes on Webhook Table
|
||||
migrator.alter_column_type(
|
||||
Webhooks,
|
||||
|
@ -6,7 +6,6 @@ import logging
|
||||
from app.classes.shared.console import Console
|
||||
from app.classes.shared.migration import Migrator, MigrateHistory
|
||||
from app.classes.models.management import (
|
||||
AuditLog,
|
||||
Webhooks,
|
||||
Schedules,
|
||||
Backups,
|
||||
@ -73,20 +72,6 @@ def migrate(migrator: Migrator, database, **kwargs):
|
||||
try:
|
||||
logger.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
|
||||
for webhook in Webhooks.select():
|
||||
@ -247,21 +232,6 @@ def rollback(migrator: Migrator, database, **kwargs):
|
||||
try:
|
||||
logger.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
|
||||
for webhook in Webhooks.select():
|
||||
old_server_id = webhook.server_id_id
|
||||
|
17
app/migrations/20240317_apikey_full_access.py
Normal file
17
app/migrations/20240317_apikey_full_access.py
Normal 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.
|
||||
"""
|
34
app/migrations/20240420_audit_log_drop.py
Normal file
34
app/migrations/20240420_audit_log_drop.py
Normal 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)
|
@ -20,6 +20,7 @@
|
||||
"created": "Vytvořen",
|
||||
"deleteKeyConfirmation": "Chcete tento API klíč odstranit? Tuto akci nelze vrátit zpět.",
|
||||
"deleteKeyConfirmationTitle": "Odstranit klíč API ${keyId}?",
|
||||
"fullAccess": "všechno",
|
||||
"getToken": "Získat token",
|
||||
"name": "Jméno",
|
||||
"nameDesc": "Jak chcete nazvat tento token API? ",
|
||||
@ -192,11 +193,14 @@
|
||||
},
|
||||
"thousands": " ",
|
||||
"zeroRecords": "Nebyly nalezeny žádné odpovídající záznamy"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Načítání..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Souhlasím",
|
||||
"bedrockError": "Stažení Bedrocku není dostupné. Prosím zkontrolujte",
|
||||
"bigBucket1": "Big Bucket Kontrola stavu selhala. Prosím zkontrolujte jej",
|
||||
"bigBucket2": "pro nejnovější informace.",
|
||||
"cancel": "Zrušit",
|
||||
"contact": "Kontaktujte podporu Crafty přes Discord",
|
||||
"craftyStatus": "Crafty stav systémů",
|
||||
@ -218,6 +222,8 @@
|
||||
"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.",
|
||||
"privMsg": "a ",
|
||||
"return": "vrátit se na hlavní stránku",
|
||||
"selfHost": "Pokud Hostujete sami toto uložiště prosím zkontrolujte adresu nebo si přečtěte náš průvodce odstraňováním problémů.",
|
||||
"serverJars1": "Server JAR api je nepřístupná. Prosím zkontrolujte",
|
||||
"serverJars2": "pro aktualní informace.",
|
||||
"start-error": "Server {} se nepodařilo spustit s kódem chyby: {}",
|
||||
@ -612,13 +618,14 @@
|
||||
"credits": "Zásluhy",
|
||||
"dashboard": "Ovládací panel",
|
||||
"documentation": "Dokumentace",
|
||||
"inApp": "V app dokumentaci",
|
||||
"inApp": "V lokalní dokumentaci",
|
||||
"navigation": "Navigace",
|
||||
"newServer": "Vytvořit nový server",
|
||||
"servers": "Servery"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Dokončuji. Držte se...",
|
||||
"cache": "Znovu načítam mezipaměť Big Bucket",
|
||||
"internals": "Nastavuji a startuji Crafty interní komponenty",
|
||||
"internet": "Kontroluju připojení k internetu",
|
||||
"server": "Konfigurace ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Erstellt",
|
||||
"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}?",
|
||||
"fullAccess": "Vollzugriff",
|
||||
"getToken": "Schlüssel erhalten",
|
||||
"name": "Name",
|
||||
"nameDesc": "Wie soll der API Schlüssel genannt werden? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ".",
|
||||
"zeroRecords": "Keine passenden Einträge gefunden"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Laden..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Zustimmen",
|
||||
"bedrockError": "Bedrock-Downloads sind nicht verfügbar. Bitte überprüfen Sie",
|
||||
"bigBucket1": "Big Bucket Zustandsprüfung fehlgeschlagen. Bitte Überprüfen",
|
||||
"bigBucket2": "für die aktuellsten Informationen.",
|
||||
"cancel": "Abbrechen",
|
||||
"contact": "Kontaktieren Sie den Crafty Control Support über Discord",
|
||||
"craftyStatus": "Crafty-Statusseite",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "und der/die/das ",
|
||||
"return": "Zurück zum Dashboard",
|
||||
"selfHost": "Wenn Sie dieses Repo selbst hosten, überprüfen Sie bitte Ihre Adresse oder konsultieren Sie unsere Anleitung zur Fehlerbehebung.",
|
||||
"serverJars1": "Server-JAR-API nicht erreichbar. Bitte überprüfen Sie ",
|
||||
"serverJars2": "um die aktuellsten Informationen zu erhalten.",
|
||||
"start-error": "Der Server {} konnte wegen dem Fehlercode: {} nicht gestartet werden",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Nur noch einen Moment, fast geschafft",
|
||||
"cache": "Aktualisieren der Big Bucket-Cache-Datei",
|
||||
"internals": "Crafty's interne Komponneten initialisieren und starten",
|
||||
"internet": "Verbindung zum Internet überprüfen",
|
||||
"server": "initialisieren ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Created",
|
||||
"deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.",
|
||||
"deleteKeyConfirmationTitle": "Remove API key ${keyId}?",
|
||||
"fullAccess": "Full Access",
|
||||
"getToken": "Get A Token",
|
||||
"name": "Name",
|
||||
"nameDesc": "What would you like to call this API token? ",
|
||||
@ -28,7 +29,6 @@
|
||||
"permName": "Permission Name",
|
||||
"perms": "Permissions",
|
||||
"server": "Server: ",
|
||||
"superUser": "Super User",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"base": {
|
||||
@ -177,11 +177,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "No matching records found"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Loading..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Agree",
|
||||
"bedrockError": "Bedrock downloads unavailable. Please check",
|
||||
"bigBucket1": "Big Bucket Health Check Failed. Please check",
|
||||
"bigBucket2": "for the most up to date information.",
|
||||
"cancel": "Cancel",
|
||||
"contact": "Contact Crafty Control Support via Discord",
|
||||
"craftyStatus": "Crafty's status page",
|
||||
@ -203,8 +206,8 @@
|
||||
"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.",
|
||||
"privMsg": "and the ",
|
||||
"serverJars1": "Server JARs API unreachable. Please check",
|
||||
"serverJars2": "for the most up to date information.",
|
||||
"return": "Return to Dashboard",
|
||||
"selfHost": "If you are self-hosting this repo please check your address or consult our troubleshooting guide.",
|
||||
"start-error": "Server {} failed to start with error code: {}",
|
||||
"superError": "You must be a super user to complete this action.",
|
||||
"terribleFailure": "What a Terrible Failure!"
|
||||
@ -607,6 +610,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Finishing up. Hang on tight...",
|
||||
"cache": "Refreshing Big Bucket cache file",
|
||||
"internals": "Configuring and starting Crafty's internal components",
|
||||
"internet": "Checking for internet connection",
|
||||
"server": "Initializing ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Creado",
|
||||
"deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.",
|
||||
"deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?",
|
||||
"fullAccess": "Acceso completo",
|
||||
"getToken": "Conseguir un Token",
|
||||
"name": "Nombre",
|
||||
"nameDesc": "¿Como te gustaría llamar a este Token de API? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "No se encontraron registros que coincidan"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Cargando..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Aceptar",
|
||||
"bedrockError": "Descargas de Bedrock no disponibles. por favor, compruebe",
|
||||
"bigBucket1": "La verificación de estado de Big Bucket ha fallado. Por favor, verifica",
|
||||
"bigBucket2": "para obtener la información más actualizada.",
|
||||
"cancel": "Cancelar",
|
||||
"contact": "Contacta el soporte de Crafty Control desde Discord",
|
||||
"craftyStatus": "Página de estados de Crafty",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "y el ",
|
||||
"return": "Volver al panel de control",
|
||||
"selfHost": "Si estás autoalojando este repositorio, revisa tu dirección o consulta nuestra guía de solución de problemas.",
|
||||
"serverJars1": "API de Servidor JAR no disponible. por favor, compruebe",
|
||||
"serverJars2": "para la información más actualizada.",
|
||||
"start-error": "Servidor {} fallo al iniciar con código de error: {}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Terminando. Espera un momento...",
|
||||
"cache": "Actualizando el archivo de caché de Big Bucket",
|
||||
"internals": "Configurando e inicializando los componentes internos de Crafty",
|
||||
"internet": "Verificando conexion a internet",
|
||||
"server": "Inicializando ",
|
||||
|
@ -100,6 +100,7 @@
|
||||
"welcome": "Tervetuloa Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Ladataan...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": lajittele sarake nousevasti",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Crée",
|
||||
"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}?",
|
||||
"fullAccess": "Accès Complet",
|
||||
"getToken": "Obtenir un Jeton",
|
||||
"name": "Nom",
|
||||
"nameDesc": "Comment appeler ce Jeton d'API ? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Aucun enregistrement correspondant trouvcé"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Chargement ..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Agree",
|
||||
"bedrockError": "Téléchargement Bedrock non disponible. Merci de vérifier",
|
||||
"bigBucket1": "Echec de vérification de l'état de Big Bucket. Veuillez vérifier",
|
||||
"bigBucket2": " pour l'information la plus à jour.",
|
||||
"cancel": "Annuler",
|
||||
"contact": "Contacter le Support de Crafty Control via Discord",
|
||||
"craftyStatus": "Page de statut de Crafty",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "et le ",
|
||||
"return": "Revenir au Tableau de Bord",
|
||||
"selfHost": "Si vous hébergez vous-même ce repo, veuillez vérifier votre adresse et votre guide de dépannage.",
|
||||
"serverJars1": "l'API Server JARs est inaccessible. Merci de vérifier",
|
||||
"serverJars2": "pour les informations les plus à jour.",
|
||||
"start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Finalisation. Patienter ...",
|
||||
"cache": "Mise à jour du fichier cache de Big Bucket",
|
||||
"internals": "Configuration et Démarrage des composants internes de Crafty",
|
||||
"internet": "Vérification de la connexion à Internet",
|
||||
"server": "Initialisation ",
|
||||
|
@ -99,6 +99,7 @@
|
||||
"welcome": "Wolkom by Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Laden...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": aktivearje om kolom oprinnend te sortearjen",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "נוצר",
|
||||
"deleteKeyConfirmation": "האם ברצונך למחוק מפתח API זה? אי אפשר לבטל את זה.",
|
||||
"deleteKeyConfirmationTitle": "? ${keyId} API-להסיר את מפתח ה",
|
||||
"fullAccess": "גישה מלאה להכל",
|
||||
"getToken": "קבלת אסימון",
|
||||
"name": "שם",
|
||||
"nameDesc": "הזה API-איך תרצו לקרוא לאסימון ה",
|
||||
@ -177,7 +178,8 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "לא נמצאו תוצאות תואמות"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "...טוען"
|
||||
},
|
||||
"error": {
|
||||
"agree": "מסכים",
|
||||
@ -203,6 +205,7 @@
|
||||
"not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?",
|
||||
"portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.",
|
||||
"privMsg": "וה",
|
||||
"return": "חזרה לפאנל",
|
||||
"serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק",
|
||||
"serverJars2": "למידע מעודכן ביותר.",
|
||||
"start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}",
|
||||
|
@ -99,6 +99,7 @@
|
||||
"welcome": "Dobrodošli u Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Učitavanje...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": aktiviraj za sortiranje stupca uzlazno",
|
||||
|
@ -100,6 +100,7 @@
|
||||
"welcome": "Selamat Datang Di Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Loading...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": aktifkan untuk mengurutkan kolom menaik",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Creato",
|
||||
"deleteKeyConfirmation": "Vuoi cancellare questa chiave API? Non puoi tornare indietro.",
|
||||
"deleteKeyConfirmationTitle": "Rimuovere la chiave API ${keyId}?",
|
||||
"fullAccess": " Accesso completo",
|
||||
"getToken": "Prendi un Token",
|
||||
"name": "Nome",
|
||||
"nameDesc": "Come desideri chiamare questo Token API? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Nessun record corrispondente trovato"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Carico..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Conferma",
|
||||
"bedrockError": "I download di Bedrock non sono disponibili. Si prega di controllare",
|
||||
"bigBucket1": "Controllo integrità di Big Bucket fallito. Controlla nuovamente",
|
||||
"bigBucket2": "per informazioni più aggiornate.",
|
||||
"cancel": "Annulla",
|
||||
"contact": "Contact Crafty Control Support via Discord",
|
||||
"craftyStatus": "Stato di Crafty",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "e il ",
|
||||
"return": "Torna alla pagina iniziale",
|
||||
"selfHost": "se stai ospitando te questo repo, controlla il tuo indirizzo o consulta la nostra guida di risoluzione dei problemi.",
|
||||
"serverJars1": "API JAR del server non raggiungibile. Si prega di controllare",
|
||||
"serverJars2": "per informazioni più aggiornate.",
|
||||
"start-error": "Server {} failed to start with error code: {}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Finalizzazione. Tieniti forte...",
|
||||
"cache": "Aggiornamento cache di Big Bucket",
|
||||
"internals": "Configurazione e avvio dei componenti interni di Crafty",
|
||||
"internet": "Controllo connessione a internet",
|
||||
"server": "Inizializzazione ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "CREATED",
|
||||
"deleteKeyConfirmation": "U SURE U WANTZ TO DELETE DIS? CAN'T UNDO!",
|
||||
"deleteKeyConfirmationTitle": "I CAN EATZ IT??? : ${keyId}?",
|
||||
"fullAccess": "All da Doors Open",
|
||||
"getToken": "GIT TOKEN",
|
||||
"name": "NAME",
|
||||
"nameDesc": "WUT WUD U LIEK 2 CALL DIS API TOKEN? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "No matching records found"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Loading..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "YESH PLS",
|
||||
"bedrockError": "BEDROCKZ DOWNLOADZ NO WORKY. CHECK PLZ.",
|
||||
"bigBucket1": "Big Bucket No Worky. Plz check",
|
||||
"bigBucket2": "for da freshest nooz.",
|
||||
"cancel": "NOPEZ",
|
||||
"contact": "CONTACK CWAFTY CONTROLLR SUPORT ON DA DIZORD",
|
||||
"craftyStatus": "CWAFTY'S STATUSZ",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "AND THEEZ ",
|
||||
"return": "Go Bak to Dashbored",
|
||||
"selfHost": "If u iz self-hostin' dis repo, check ur addy or peep our fix-it guide.",
|
||||
"serverJars1": "CAN'T TALK TO SERVER JARS API. CHECKZ",
|
||||
"serverJars2": "TO SEE NEWZ STUFFZ.",
|
||||
"start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "ALMOST DUN. HOLD ON TO YER WHISKERS...",
|
||||
"cache": "Fightin' for a Big Bucket o Fish",
|
||||
"internals": "SETTIN' UP AN' STARTIN' CWAFTY'S INSIDE BITZ",
|
||||
"internet": "LOOKIN' FOR OUTER SPACE TALKY",
|
||||
"server": "WAKIN' UPZ ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Izveidots",
|
||||
"deleteKeyConfirmation": "Vai vēlies dzēst šo API atslēgu? Šo nevar atdarīt.",
|
||||
"deleteKeyConfirmationTitle": "Noņemt API atslēgu ${keyId}?",
|
||||
"fullAccess": "Pilna piekļuve",
|
||||
"getToken": "Saņemt Pilnvaru (Token)",
|
||||
"name": "Nosaukums",
|
||||
"nameDesc": "Kā jūs vēlaties nosaukt šo Pilnvaru (Token)? ",
|
||||
@ -178,11 +179,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Nav atrasti atbilstoši ieraksti"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Ielādē..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Piekrītu",
|
||||
"bedrockError": "Bedrock lejupielādes nav pieejamas. Lūdzu pārbaudi",
|
||||
"bigBucket1": "Big Bucket stāvokļa pārbaude neizdevās. Lūdzu izpētiet",
|
||||
"bigBucket2": "priekš jaunākās informācijas.",
|
||||
"cancel": "Atcelt",
|
||||
"contact": "Sazinies ar Crafty Control Atbalstu izmantojot Discord",
|
||||
"craftyStatus": "Crafty statusa lapa",
|
||||
@ -204,6 +208,8 @@
|
||||
"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.",
|
||||
"privMsg": "un ",
|
||||
"return": "Atgriezties uz pārskatu",
|
||||
"selfHost": "Ja jūs paši uzturat šo repozitoriju, pārbaudiet savu adresi vai apskatiet mūsu kļūdu novēršanas dokumentāciju.",
|
||||
"serverJars1": "Serveru JAR API nav sasniedzams. Lūdzu pārbaudiet",
|
||||
"serverJars2": "priekš jaunākās informācijas.",
|
||||
"start-error": "Serveris {} neveiskmīgi startējās ar kļūdas kodu: {}",
|
||||
@ -601,6 +607,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Pabeidz. Vēl tik nedaudz...",
|
||||
"cache": "Atjauno Big Bucket keša failu",
|
||||
"internals": "Konfigurē un Startē Crafty iekšējās komponenetes",
|
||||
"internet": "Pārbauda interneta savienojumu",
|
||||
"server": "Inicializē ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Gecreëerd",
|
||||
"deleteKeyConfirmation": "Wilt u deze API sleutel verwijderen? Dit kan niet ongedaan gemaakt worden.",
|
||||
"deleteKeyConfirmationTitle": "API sleutel verwijderen ${keyId}?",
|
||||
"fullAccess": "Volledige toegang",
|
||||
"getToken": "Verkrijg een Token",
|
||||
"name": "Naam",
|
||||
"nameDesc": "Hoe wilt u dit API token noemen? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Geen overeenkomende records gevonden"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Bezig met laden..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Akkoord",
|
||||
"bedrockError": "Bedrock-downloads niet beschikbaar. Controleer alstublieft",
|
||||
"bigBucket1": "Big Bucket-gezondheidscontrole mislukt. Controleer alstublieft",
|
||||
"bigBucket2": "voor de meest recente informatie.",
|
||||
"cancel": "Annuleren",
|
||||
"contact": "Neem contact op met Crafty Control ondersteuning via Discord",
|
||||
"craftyStatus": "Crafty's statuspagina",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "en de ",
|
||||
"return": "Terug naar Dashboard",
|
||||
"selfHost": "Als u deze repository zelf host, controleer dan uw adres of raadpleeg onze handleiding voor probleemoplossing.",
|
||||
"serverJars1": "Server JARs API niet bereikbaar. Controleer alstublieft",
|
||||
"serverJars2": "voor de meest recente informatie.",
|
||||
"start-error": "Server {} kan niet starten met foutcode: {}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "De laatste hand leggen. Houd je vast...",
|
||||
"cache": "Big Bucket-cachebestand vernieuwen",
|
||||
"internals": "Crafty's interne componenten configureren en starten",
|
||||
"internet": "Controleren op internetverbinding",
|
||||
"server": "Initialiseren ",
|
||||
|
@ -99,6 +99,7 @@
|
||||
"welcome": "Welkom bij Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Laden...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": activeer om kolom oplopend te sorteren",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Stworzono",
|
||||
"deleteKeyConfirmation": "Czy chcesz usunąć ten klucz API? Nie można tego cofnąć.",
|
||||
"deleteKeyConfirmationTitle": "Usunąć Klucz API ${keyId}?",
|
||||
"fullAccess": "Pełny dostęp",
|
||||
"getToken": "Zdobądź token",
|
||||
"name": "Nazwa",
|
||||
"nameDesc": "Jak chcesz nazwać ten klucz API? ",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Nie znaleziono pasujacego wyniku"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Wczytywanie..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Zgadzam się",
|
||||
"bedrockError": "Pobieranie serwera bedrock jest teraz niedostępne. Proszę sprawdź",
|
||||
"bigBucket1": "Odświeżanie Big Bucket nie powiodło się. Sprawdź",
|
||||
"bigBucket2": "dla najnowszych informacji.",
|
||||
"cancel": "Anuluj",
|
||||
"contact": "Podrzebujesz pomocy? Zapraszamy na serwer discord Crafty Controler",
|
||||
"craftyStatus": "Status strony Craftyiego",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "i także ",
|
||||
"return": "Powrót do panelu",
|
||||
"selfHost": "Jeśli zarządasz tą repozytorią upewnij się że adres hest poprawny, w innym przypadku odwiedź strone rozwiązywania problemów.",
|
||||
"serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź",
|
||||
"serverJars2": "dla najnowzsych informacji.",
|
||||
"start-error": "Serwer {} nie mógł się odpalić z powodu: {}",
|
||||
@ -599,6 +605,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Prawie gotowe! Jeszcze tylko chwilka...",
|
||||
"cache": "Odświeżanie pamięci podręcznej Big Bucket",
|
||||
"internals": "Konfigurowanie i włączanie backendu...",
|
||||
"internet": "Sprawdzam połączenie z internetem",
|
||||
"server": "Włączanie ",
|
||||
|
@ -100,6 +100,7 @@
|
||||
"welcome": "Bem-vindo ao Crafty Controller"
|
||||
},
|
||||
"datatables": {
|
||||
"loadingRecords": "Carregando...",
|
||||
"i18n": {
|
||||
"aria": {
|
||||
"sortAscending": ": ative para ordenar a coluna de forma ascendente",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "สร้างเมื่อ",
|
||||
"deleteKeyConfirmation": "คุณต้องการลบคีย์ API นี้หรือไม่ สิ่งนี้ไม่สามารถยกเลิกได้",
|
||||
"deleteKeyConfirmationTitle": "ลบคีย์ API นี้ ${keyId} หรือไม่?",
|
||||
"fullAccess": "เข้าถึงได้ทั้งหมด",
|
||||
"getToken": "แสดงโทเค็น",
|
||||
"name": "ชื่อ",
|
||||
"nameDesc": "คุณต้องการเรียกโทเค็น API นี้ว่าอะไร ? ",
|
||||
@ -177,7 +178,8 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "ไม่พบรายการที่ตรงกัน"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "กำลังโหลด..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "ยอมรับ",
|
||||
@ -203,6 +205,7 @@
|
||||
"not-downloaded": "ดูเหมือนว่าเราจะไม่พบแฟ้มกระทำการของคุณ (.jar) ตรวจสอบให้แน่ใจว่าการดาวโหลดน์เสร็จสิ้นแล้ว, การอนุญาตถูกตั้งไปยังแฟ้มกระทำการหรือไม่?",
|
||||
"portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล",
|
||||
"privMsg": "และ ",
|
||||
"return": "ย้อนกลับไปยังแผงควบคุม",
|
||||
"serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ",
|
||||
"serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด",
|
||||
"start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Oluşturuldu",
|
||||
"deleteKeyConfirmation": "Bu API anahtarını silmek istediğine emin misin? Bu geri alınamaz.",
|
||||
"deleteKeyConfirmationTitle": "${keyId} API anahtarını kaldırma işlemi.",
|
||||
"fullAccess": "Tam Erişim",
|
||||
"getToken": "Bir Token Al",
|
||||
"name": "Ad",
|
||||
"nameDesc": "Bu API tokeninin adı ne olsun?",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ".",
|
||||
"zeroRecords": "Eşleşen kayıt bulunamadı"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Yükleniyor..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Kabul Et",
|
||||
"bedrockError": "Bedrock indirmeleri kullanılamıyor. Lütfen kontrol edin",
|
||||
"bigBucket1": "Big Bucket sağlık kontrolü yapılamadı. En güncel bilgileri burada bulabilirsiniz:",
|
||||
"bigBucket2": ".",
|
||||
"cancel": "İptal",
|
||||
"contact": "Crafty Control Destek Discord Sunucusu",
|
||||
"craftyStatus": "Crafty'nin durum sayfası",
|
||||
@ -203,6 +207,8 @@
|
||||
"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.",
|
||||
"privMsg": "ve ",
|
||||
"return": "Arayüze Geri Dön",
|
||||
"selfHost": "Bu depoyu kendiniz barındırıyorsanız lütfen adresinizi kontrol ediniz veya sorun giderme kılavuzumuza bakınız.",
|
||||
"serverJars1": "Sunucu JARs API'ına erişilemiyor.",
|
||||
"serverJars2": "en güncel bilgilere sahiptir",
|
||||
"start-error": "{} sunucusu başlamatılamadı. Hata kodu: {}",
|
||||
@ -599,6 +605,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Bitiriliyor. Sıkı tutun...",
|
||||
"cache": "Big Bucket önbellek dosyası yenileniyor",
|
||||
"internals": "Crafty'nin dahili bileşenlerini konfigüre etme ve başlatma",
|
||||
"internet": "İnternet bağlantısı kontrol ediliyor",
|
||||
"server": "Başlatılıyor ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "Створений",
|
||||
"deleteKeyConfirmation": "Ви дійсно бажаєте видалити API ключ? Це незворотня дія.",
|
||||
"deleteKeyConfirmationTitle": "Видалення API ключ ${keyId}?",
|
||||
"fullAccess": "Повний доступ",
|
||||
"getToken": "Отримати Токен",
|
||||
"name": "Ім'я",
|
||||
"nameDesc": "Як ви хочете назвати даний API токен?",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "Не знайдено збігів в базі"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "Завантаження..."
|
||||
},
|
||||
"error": {
|
||||
"agree": "Згодний",
|
||||
"bedrockError": "Bedrock недоступний для скачування. Перевірте будь ласка",
|
||||
"bigBucket1": "Перевірка Big Bucket не пройдено. Будь ласка, перевірте",
|
||||
"bigBucket2": "для найактуальнішої інформації.",
|
||||
"cancel": "Відміна",
|
||||
"contact": "Зв'язатись з Crafty Control підтримкою через Discord",
|
||||
"craftyStatus": "Crafty's статус",
|
||||
@ -203,6 +207,8 @@
|
||||
"not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?",
|
||||
"portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.",
|
||||
"privMsg": "і ",
|
||||
"return": "Повернутись до панелі",
|
||||
"selfHost": "Якщо ви самостійно розміщуєте цей репозеторій, перевірте свою адресу або зверніться до нашого посібника з усунення несправностей.",
|
||||
"serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте",
|
||||
"serverJars2": "для найактуальнішої інформації.",
|
||||
"start-error": "Сервер {} не запустився через помилку: {}",
|
||||
@ -599,6 +605,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Закінчуємо. Тримайся міцніше...",
|
||||
"cache": "Оновлення файлу кешу Big Bucket",
|
||||
"internals": "Налаштування та запуск внутрішніх компонентів Crafty ",
|
||||
"internet": "Перевірка доступу до інтернету",
|
||||
"server": "Ініціалізація ",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"created": "创建时间",
|
||||
"deleteKeyConfirmation": "您想要删除这个 API 密钥吗?此操作不能撤销。",
|
||||
"deleteKeyConfirmationTitle": "删除 API 密钥 ${keyId}?",
|
||||
"fullAccess": "完全访问",
|
||||
"getToken": "获得一个令牌",
|
||||
"name": "名称",
|
||||
"nameDesc": "你想把这个 API 令牌叫做什么?",
|
||||
@ -177,11 +178,14 @@
|
||||
},
|
||||
"thousands": ",",
|
||||
"zeroRecords": "没有找到匹配的记录"
|
||||
}
|
||||
},
|
||||
"loadingRecords": "正在加载……"
|
||||
},
|
||||
"error": {
|
||||
"agree": "同意",
|
||||
"bedrockError": "基岩版下载不可用。请检查",
|
||||
"bigBucket1": "Big Bucket 查活失败。请检查",
|
||||
"bigBucket2": "以获取最新信息。",
|
||||
"cancel": "取消",
|
||||
"contact": "通过 Discord 联系 Crafty Control 支持",
|
||||
"craftyStatus": "Crafty 的状态页面",
|
||||
@ -203,6 +207,8 @@
|
||||
"not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?",
|
||||
"portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。",
|
||||
"privMsg": "以及",
|
||||
"return": "返回仪表板",
|
||||
"selfHost": "如果您自托管此仓库,请检查您的地址或参考我们的故障排除指南。",
|
||||
"serverJars1": "无法访问服务器 JAR API。请检查",
|
||||
"serverJars2": "以获取最新信息。",
|
||||
"start-error": "服务器 {} 启动失败,错误代码为:{}",
|
||||
@ -600,6 +606,7 @@
|
||||
},
|
||||
"startup": {
|
||||
"almost": "即将完成。请稍候……",
|
||||
"cache": "正在刷新 Big Bucket 缓存文件",
|
||||
"internals": "正在配置并启动 Crafty 的内部组件",
|
||||
"internet": "正在检查网络连接",
|
||||
"server": "正在初始化 ",
|
||||
|
14
main.py
14
main.py
@ -17,6 +17,7 @@ from app.classes.models.users import HelperUsers
|
||||
from app.classes.models.management import HelpersManagement
|
||||
from app.classes.shared.import_helper import ImportHelpers
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
from app.classes.logging.log_formatter import JsonFormatter
|
||||
|
||||
console = Console()
|
||||
helper = Helpers()
|
||||
@ -117,7 +118,7 @@ def controller_setup():
|
||||
def tasks_starter():
|
||||
"""
|
||||
Method starts stats recording, app scheduler, and
|
||||
serverjars/steamCMD cache refreshers
|
||||
big bucket/steamCMD cache refreshers
|
||||
"""
|
||||
# start stats logging
|
||||
tasks_manager.start_stats_recording()
|
||||
@ -127,8 +128,8 @@ def tasks_starter():
|
||||
tasks_manager.start_scheduler()
|
||||
|
||||
# refresh our cache and schedule for every 12 hoursour cache refresh
|
||||
# for serverjars.com
|
||||
tasks_manager.serverjar_cache_refresher()
|
||||
# for big bucket.com
|
||||
tasks_manager.big_bucket_cache_refresher()
|
||||
|
||||
|
||||
def signal_handler(signum, _frame):
|
||||
@ -212,6 +213,8 @@ def setup_starter():
|
||||
time.sleep(2)
|
||||
controller_setup_thread.start()
|
||||
|
||||
web_sock.broadcast("update", {"section": "cache"})
|
||||
controller.big_bucket.manual_refresh_cache()
|
||||
# Wait for the setup threads to finish
|
||||
web_sock.broadcast(
|
||||
"update",
|
||||
@ -284,6 +287,11 @@ def setup_logging(debug=True):
|
||||
|
||||
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:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.warning(f"Unable to read logging config from {logging_config_file}")
|
||||
|
@ -3,7 +3,7 @@ sonar.organization=crafty-controller
|
||||
|
||||
# This is the name and version displayed in the SonarCloud UI.
|
||||
sonar.projectName=Crafty 4
|
||||
sonar.projectVersion=4.3.3
|
||||
sonar.projectVersion=4.4.1
|
||||
sonar.python.version=3.9, 3.10, 3.11
|
||||
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user