feat: surface remnawave panel webhook settings

This commit is contained in:
3252a8
2026-05-29 23:55:12 +03:00
parent 604ba873dc
commit 49781af921
6 changed files with 70 additions and 14 deletions
+25 -10
View File
@@ -30,6 +30,11 @@ class SettingField:
i18n_label_key: Optional[str] = None
i18n_description_key: Optional[str] = None
i18n_subsection_key: Optional[str] = None
webhook_path: Optional[str] = None
webhook_requires_base_url: bool = False
webhook_provider_id: Optional[str] = None
webhook_hint_i18n_key: Optional[str] = None
webhook_hint: str = ""
SETTINGS_MANIFEST: List[SettingField] = [
@@ -84,44 +89,44 @@ SETTINGS_MANIFEST: List[SettingField] = [
SettingField(
"PANEL_API_URL",
"url",
"general",
"remnawave",
"URL API Remnawave",
"Например, https://panel.example.com/api.",
subsection="Remnawave",
),
SettingField(
"PANEL_API_KEY",
"string",
"general",
"remnawave",
"API-ключ Remnawave",
"Секретный ключ API панели.",
secret=True,
subsection="Remnawave",
),
SettingField(
"PANEL_WEBHOOK_SECRET",
"string",
"general",
"remnawave",
"Секрет вебхуков Remnawave",
"Используется для проверки входящих вебхуков панели.",
secret=True,
subsection="Remnawave",
webhook_path="/webhook/panel",
webhook_requires_base_url=True,
webhook_provider_id="remnawave",
webhook_hint_i18n_key="admin_settings_panel_webhook_url_hint",
webhook_hint="Use this URL as WEBHOOK_URL in Remnawave Panel.",
),
SettingField(
"USER_SQUAD_UUIDS",
"string",
"general",
"remnawave",
"Internal Squads по умолчанию",
"UUID через запятую для legacy-режима без JSON-каталога тарифов.",
subsection="Remnawave",
),
SettingField(
"USER_EXTERNAL_SQUAD_UUID",
"string",
"general",
"remnawave",
"External Squad по умолчанию",
"Необязательный UUID External Squad для новых пользователей.",
subsection="Remnawave",
),
# ─── Web app appearance ────────────────────────────────────────
SettingField(
@@ -707,6 +712,7 @@ def manifest_payload() -> List[dict]:
sections_order = {
"general": 1,
"appearance": 2,
"remnawave": 3,
"pricing": 11,
"payments": 4,
"trial": 5,
@@ -773,6 +779,15 @@ def manifest_payload() -> List[dict]:
item["default"] = default_value
if webhook_metadata:
item.update(webhook_metadata)
if field.webhook_path:
item["webhook_path"] = field.webhook_path
item["webhook_requires_base_url"] = field.webhook_requires_base_url
if field.webhook_provider_id:
item["provider_id"] = field.webhook_provider_id
if field.webhook_hint_i18n_key:
item["webhook_hint_i18n_key"] = field.webhook_hint_i18n_key
if field.webhook_hint:
item["webhook_hint"] = field.webhook_hint
if field.choices:
item["choices"] = [
{
@@ -198,6 +198,8 @@
url,
requiresBaseUrl: Boolean(field.webhook_requires_base_url),
baseConfigured: field.webhook_base_url_configured !== false,
hintI18nKey: field.webhook_hint_i18n_key || "",
hintFallback: field.webhook_hint || "",
};
}
@@ -333,6 +335,7 @@
function sectionTitle(id) {
const map = {
general: "Общие",
remnawave: "Remnawave Panel",
appearance: "Внешний вид",
pricing: "Тарифы и цены",
payments: "Платёжные системы",
@@ -415,9 +418,9 @@
<small>
{webhook.url
? at(
"settings_provider_webhook_url_hint",
adminLocaleKey(webhook.hintI18nKey || "settings_provider_webhook_url_hint"),
{},
"Use this URL in the provider webhook settings."
webhook.hintFallback || "Use this URL in the provider webhook settings."
)
: at(
"settings_provider_webhook_base_missing",
+3 -1
View File
@@ -1017,6 +1017,7 @@
"admin_content": "Content",
"admin_logs_empty": "No entries",
"admin_settings_section_general": "General",
"admin_settings_section_remnawave": "Remnawave Panel",
"admin_settings_section_appearance": "Appearance",
"admin_settings_section_pricing": "Legacy tariff compatibility",
"admin_settings_section_payments": "Payment systems",
@@ -1039,6 +1040,7 @@
"admin_settings_subsection_heleket": "Heleket",
"admin_settings_provider_webhook_url": "Webhook URL",
"admin_settings_provider_webhook_url_hint": "Use this URL in the provider webhook settings.",
"admin_settings_panel_webhook_url_hint": "Use this URL as WEBHOOK_URL in Remnawave Panel.",
"admin_settings_provider_webhook_base_missing": "Set WEBHOOK_BASE_URL in .env to show the full URL for {path}.",
"admin_settings_provider_admin_only_label": "Only for admins",
"admin_settings_provider_admin_only_description": "Shows this provider only to admins. Webhooks and payment status handling remain active for test payments.",
@@ -1437,7 +1439,7 @@
"admin_settings_field_panel_api_key_label": "Remnawave API key",
"admin_settings_field_panel_api_key_description": "Secret API key for the panel.",
"admin_settings_field_panel_webhook_secret_label": "Remnawave webhook secret",
"admin_settings_field_panel_webhook_secret_description": "Used to verify incoming panel webhooks.",
"admin_settings_field_panel_webhook_secret_description": "Set the secret in Remnawave Panel and paste the same value here to verify incoming panel webhooks.",
"admin_settings_field_user_squad_uuids_label": "Default Internal Squads",
"admin_settings_field_user_squad_uuids_description": "Comma-separated UUIDs for the legacy mode without a JSON tariff catalog.",
"admin_settings_field_user_external_squad_uuid_label": "Default External Squad",
+3 -1
View File
@@ -1017,6 +1017,7 @@
"admin_content": "Контент",
"admin_logs_empty": "Записей нет",
"admin_settings_section_general": "Общие",
"admin_settings_section_remnawave": "Remnawave Panel",
"admin_settings_section_appearance": "Внешний вид",
"admin_settings_section_pricing": "Совместимость с legacy-тарифами",
"admin_settings_section_payments": "Платёжные системы",
@@ -1039,6 +1040,7 @@
"admin_settings_subsection_heleket": "Heleket",
"admin_settings_provider_webhook_url": "Webhook URL",
"admin_settings_provider_webhook_url_hint": "Укажите этот адрес в настройках вебхуков провайдера.",
"admin_settings_panel_webhook_url_hint": "Укажите этот адрес как WEBHOOK_URL в Remnawave Panel.",
"admin_settings_provider_webhook_base_missing": "Укажите WEBHOOK_BASE_URL в .env, чтобы увидеть полный адрес для {path}.",
"admin_settings_provider_admin_only_label": "Только для админов",
"admin_settings_provider_admin_only_description": "Показывает провайдер только администраторам. Вебхуки и обработка статусов остаются активными для тестовых платежей.",
@@ -1437,7 +1439,7 @@
"admin_settings_field_panel_api_key_label": "API-ключ Remnawave",
"admin_settings_field_panel_api_key_description": "Секретный ключ API панели.",
"admin_settings_field_panel_webhook_secret_label": "Секрет вебхуков Remnawave",
"admin_settings_field_panel_webhook_secret_description": "Используется для проверки входящих вебхуков панели.",
"admin_settings_field_panel_webhook_secret_description": "Задайте секрет в Remnawave Panel и вставьте то же значение здесь для проверки входящих вебхуков панели.",
"admin_settings_field_user_squad_uuids_label": "Internal Squads по умолчанию",
"admin_settings_field_user_squad_uuids_description": "UUID через запятую для legacy-режима без JSON-каталога тарифов.",
"admin_settings_field_user_external_squad_uuid_label": "External Squad по умолчанию",
@@ -230,6 +230,32 @@ def test_payment_provider_settings_include_webhook_metadata():
assert "webhook_path" not in manifest["PAYMENT_STARS_WEBAPP_LABEL_RU"]
def test_remnawave_settings_include_panel_webhook_metadata():
manifest = _manifest_by_key()
field = manifest["PANEL_WEBHOOK_SECRET"]
remnawave_keys = (
"PANEL_API_URL",
"PANEL_API_KEY",
"PANEL_WEBHOOK_SECRET",
"USER_SQUAD_UUIDS",
"USER_EXTERNAL_SQUAD_UUID",
)
assert field["webhook_path"] == "/webhook/panel"
assert field["webhook_requires_base_url"] is True
assert field["provider_id"] == "remnawave"
assert field["webhook_hint_i18n_key"] == "admin_settings_panel_webhook_url_hint"
for setting_key in remnawave_keys:
assert manifest[setting_key]["section"] == "remnawave"
assert manifest[setting_key]["section_order"] == 3
assert manifest[setting_key]["subsection"] is None
for language in ("ru", "en"):
messages = _locale(language)
assert "admin_settings_section_remnawave" in messages
assert field["webhook_hint_i18n_key"] in messages
def test_payment_provider_admin_only_toggles_are_mutually_exclusive():
manifest = _manifest_by_key()
+8
View File
@@ -760,10 +760,13 @@ class AdminSettingsSecurityTests(unittest.IsolatedAsyncioTestCase):
response = await admin_api.admin_settings_get_route(request)
payload = json.loads(response.text)
section_ids = [section["id"] for section in payload["sections"]]
fields = {
field["key"]: field for section in payload["sections"] for field in section["fields"]
}
self.assertLess(section_ids.index("general"), section_ids.index("remnawave"))
self.assertLess(section_ids.index("remnawave"), section_ids.index("payments"))
self.assertEqual(
fields["FREEKASSA_ENABLED"]["webhook_url"],
"https://web.tnnl.cc/webhook/freekassa",
@@ -773,6 +776,11 @@ class AdminSettingsSecurityTests(unittest.IsolatedAsyncioTestCase):
fields["PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_RU"]["webhook_url"],
"https://web.tnnl.cc/webhook/platega",
)
self.assertEqual(
fields["PANEL_WEBHOOK_SECRET"]["webhook_url"],
"https://web.tnnl.cc/webhook/panel",
)
self.assertTrue(fields["PANEL_WEBHOOK_SECRET"]["webhook_requires_base_url"])
self.assertNotIn("webhook_url", fields["PAYMENT_STARS_WEBAPP_LABEL_RU"])