Merge branch 'main' of github.com:netalertx/NetAlertX

This commit is contained in:
jokob-sk
2026-02-28 15:58:52 +11:00
14 changed files with 378 additions and 119 deletions

View File

@@ -42,6 +42,7 @@ from .dbquery_endpoint import read_query, write_query, update_query, delete_quer
from .sync_endpoint import handle_sync_post, handle_sync_get # noqa: E402 [flake8 lint suppression]
from .logs_endpoint import clean_log # noqa: E402 [flake8 lint suppression]
from .health_endpoint import get_health_status # noqa: E402 [flake8 lint suppression]
from .languages_endpoint import get_languages # noqa: E402 [flake8 lint suppression]
from models.user_events_queue_instance import UserEventsQueueInstance # noqa: E402 [flake8 lint suppression]
from models.event_instance import EventInstance # noqa: E402 [flake8 lint suppression]
@@ -95,6 +96,7 @@ from .openapi.schemas import ( # noqa: E402 [flake8 lint suppression]
DbQueryUpdateRequest, DbQueryDeleteRequest,
AddToQueueRequest, GetSettingResponse,
RecentEventsRequest, SetDeviceAliasRequest,
LanguagesResponse,
)
from .sse_endpoint import ( # noqa: E402 [flake8 lint suppression]
@@ -1962,6 +1964,34 @@ def check_health(payload=None):
}), 500
@app.route("/languages", methods=["GET"])
@validate_request(
operation_id="get_languages",
summary="Get Supported Languages",
description="Returns the canonical list of supported UI languages loaded from languages.json.",
response_model=LanguagesResponse,
tags=["system", "languages"],
auth_callable=is_authorized
)
def list_languages(payload=None):
"""Return the canonical language registry."""
try:
data = get_languages()
return jsonify({"success": True, **data}), 200
except FileNotFoundError:
return jsonify({
"success": False,
"error": "languages.json not found",
"message": "Language registry file is missing"
}), 500
except ValueError as e:
return jsonify({
"success": False,
"error": str(e),
"message": "Language registry file is malformed"
}), 500
# --------------------------
# Background Server Start
# --------------------------

View File

@@ -545,7 +545,7 @@ class Query(ObjectType):
language_folder = '/app/front/php/templates/language/'
if os.path.exists(language_folder):
for filename in os.listdir(language_folder):
if filename.endswith('.json'):
if filename.endswith('.json') and filename != 'languages.json':
file_lang_code = filename.replace('.json', '')
# Filter by langCode if provided

View File

@@ -0,0 +1,43 @@
"""Languages endpoint — returns the canonical language registry from languages.json."""
import json
import os
from logger import mylog
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
LANGUAGES_JSON_PATH = os.path.join(
INSTALL_PATH, "front", "php", "templates", "language", "languages.json"
)
def get_languages():
"""
Load and return the canonical language registry.
Returns a dict with keys:
- default (str): the fallback language code
- languages (list[dict]): each entry has 'code' and 'display'
Raises:
FileNotFoundError: if languages.json is missing
ValueError: if the JSON is malformed or missing required fields
"""
try:
with open(LANGUAGES_JSON_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
mylog("none", [f"[languages] languages.json not found at {LANGUAGES_JSON_PATH}"])
raise
except json.JSONDecodeError as e:
mylog("none", [f"[languages] Failed to parse languages.json: {e}"])
raise ValueError(f"Malformed languages.json: {e}") from e
if "default" not in data or "languages" not in data:
raise ValueError("languages.json must contain 'default' and 'languages' keys")
return {
"default": data["default"],
"languages": data["languages"],
"count": len(data["languages"]),
}

View File

@@ -1031,6 +1031,41 @@ class GetSettingResponse(BaseResponse):
value: Any = Field(None, description="The setting value")
# =============================================================================
# LANGUAGES SCHEMAS
# =============================================================================
class LanguageEntry(BaseModel):
"""A single supported language entry."""
model_config = ConfigDict(extra="allow")
code: str = Field(..., description="ISO language code (e.g. 'en_us')")
display: str = Field(..., description="Human-readable display name (e.g. 'English (en_us)')")
class LanguagesResponse(BaseResponse):
"""Response for GET /languages — the canonical language registry."""
model_config = ConfigDict(
extra="allow",
json_schema_extra={
"examples": [{
"success": True,
"default": "en_us",
"count": 20,
"languages": [
{"code": "en_us", "display": "English (en_us)"},
{"code": "de_de", "display": "German (de_de)"}
]
}]
}
)
default: str = Field(..., description="Default/fallback language code")
count: int = Field(..., description="Total number of supported languages")
languages: List[LanguageEntry] = Field(..., description="All supported languages")
# =============================================================================
# GRAPHQL SCHEMAS
# =============================================================================

View File

@@ -10,7 +10,7 @@ import uuid
# Register NetAlertX libraries
import conf
from const import fullConfPath, fullConfFolder, default_tz
from const import fullConfPath, fullConfFolder, default_tz, applicationPath
from helper import getBuildTimeStampAndVersion, collect_lang_strings, updateSubnets, generate_random_string
from utils.datetime_utils import timeNowUTC
from app_state import updateState
@@ -21,6 +21,31 @@ from plugin import plugin_manager, print_plugin_info
from utils.plugin_utils import get_plugins_configs, get_set_value_for_init
from messaging.in_app import write_notification
# ===============================================================================
# Language helpers
# ===============================================================================
_LANGUAGES_JSON = os.path.join(
applicationPath, "front", "php", "templates", "language", "languages.json"
)
def _load_language_display_names():
"""Return a JSON-serialised list of display names from languages.json.
Falls back to a hardcoded English-only list on any error so that
the settings page is never broken by a missing/corrupt file.
"""
try:
with open(_LANGUAGES_JSON, "r", encoding="utf-8") as f:
data = json.load(f)
names = [entry["display"] for entry in data["languages"]]
return json.dumps(names)
except Exception as e:
mylog("none", [f"[languages] Failed to load languages.json, using fallback: {e}"])
return '["English (en_us)"]'
# ===============================================================================
# Initialise user defined values
# ===============================================================================
@@ -401,7 +426,7 @@ def importConfigs(pm, db, all_plugins):
c_d,
"Language Interface",
'{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}',
"['English (en_us)', 'Arabic (ar_ar)', 'Catalan (ca_ca)', 'Czech (cs_cz)', 'German (de_de)', 'Spanish (es_es)', 'Farsi (fa_fa)', 'French (fr_fr)', 'Italian (it_it)', 'Japanese (ja_jp)', 'Norwegian (nb_no)', 'Polish (pl_pl)', 'Portuguese (pt_br)', 'Portuguese (pt_pt)', 'Russian (ru_ru)', 'Swedish (sv_sv)', 'Turkish (tr_tr)', 'Ukrainian (uk_ua)', 'Vietnamese (vi_vn)', 'Chinese (zh_cn)']", # noqa: E501 - inline JSON
_load_language_display_names(), # derived from languages.json
"UI",
)