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_label_key: Optional[str] = None
i18n_description_key: Optional[str] = None i18n_description_key: Optional[str] = None
i18n_subsection_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] = [ SETTINGS_MANIFEST: List[SettingField] = [
@@ -84,44 +89,44 @@ SETTINGS_MANIFEST: List[SettingField] = [
SettingField( SettingField(
"PANEL_API_URL", "PANEL_API_URL",
"url", "url",
"general", "remnawave",
"URL API Remnawave", "URL API Remnawave",
"Например, https://panel.example.com/api.", "Например, https://panel.example.com/api.",
subsection="Remnawave",
), ),
SettingField( SettingField(
"PANEL_API_KEY", "PANEL_API_KEY",
"string", "string",
"general", "remnawave",
"API-ключ Remnawave", "API-ключ Remnawave",
"Секретный ключ API панели.", "Секретный ключ API панели.",
secret=True, secret=True,
subsection="Remnawave",
), ),
SettingField( SettingField(
"PANEL_WEBHOOK_SECRET", "PANEL_WEBHOOK_SECRET",
"string", "string",
"general", "remnawave",
"Секрет вебхуков Remnawave", "Секрет вебхуков Remnawave",
"Используется для проверки входящих вебхуков панели.", "Используется для проверки входящих вебхуков панели.",
secret=True, 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( SettingField(
"USER_SQUAD_UUIDS", "USER_SQUAD_UUIDS",
"string", "string",
"general", "remnawave",
"Internal Squads по умолчанию", "Internal Squads по умолчанию",
"UUID через запятую для legacy-режима без JSON-каталога тарифов.", "UUID через запятую для legacy-режима без JSON-каталога тарифов.",
subsection="Remnawave",
), ),
SettingField( SettingField(
"USER_EXTERNAL_SQUAD_UUID", "USER_EXTERNAL_SQUAD_UUID",
"string", "string",
"general", "remnawave",
"External Squad по умолчанию", "External Squad по умолчанию",
"Необязательный UUID External Squad для новых пользователей.", "Необязательный UUID External Squad для новых пользователей.",
subsection="Remnawave",
), ),
# ─── Web app appearance ──────────────────────────────────────── # ─── Web app appearance ────────────────────────────────────────
SettingField( SettingField(
@@ -707,6 +712,7 @@ def manifest_payload() -> List[dict]:
sections_order = { sections_order = {
"general": 1, "general": 1,
"appearance": 2, "appearance": 2,
"remnawave": 3,
"pricing": 11, "pricing": 11,
"payments": 4, "payments": 4,
"trial": 5, "trial": 5,
@@ -773,6 +779,15 @@ def manifest_payload() -> List[dict]:
item["default"] = default_value item["default"] = default_value
if webhook_metadata: if webhook_metadata:
item.update(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: if field.choices:
item["choices"] = [ item["choices"] = [
{ {
@@ -198,6 +198,8 @@
url, url,
requiresBaseUrl: Boolean(field.webhook_requires_base_url), requiresBaseUrl: Boolean(field.webhook_requires_base_url),
baseConfigured: field.webhook_base_url_configured !== false, baseConfigured: field.webhook_base_url_configured !== false,
hintI18nKey: field.webhook_hint_i18n_key || "",
hintFallback: field.webhook_hint || "",
}; };
} }
@@ -333,6 +335,7 @@
function sectionTitle(id) { function sectionTitle(id) {
const map = { const map = {
general: "Общие", general: "Общие",
remnawave: "Remnawave Panel",
appearance: "Внешний вид", appearance: "Внешний вид",
pricing: "Тарифы и цены", pricing: "Тарифы и цены",
payments: "Платёжные системы", payments: "Платёжные системы",
@@ -415,9 +418,9 @@
<small> <small>
{webhook.url {webhook.url
? at( ? 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( : at(
"settings_provider_webhook_base_missing", "settings_provider_webhook_base_missing",
+3 -1
View File
@@ -1017,6 +1017,7 @@
"admin_content": "Content", "admin_content": "Content",
"admin_logs_empty": "No entries", "admin_logs_empty": "No entries",
"admin_settings_section_general": "General", "admin_settings_section_general": "General",
"admin_settings_section_remnawave": "Remnawave Panel",
"admin_settings_section_appearance": "Appearance", "admin_settings_section_appearance": "Appearance",
"admin_settings_section_pricing": "Legacy tariff compatibility", "admin_settings_section_pricing": "Legacy tariff compatibility",
"admin_settings_section_payments": "Payment systems", "admin_settings_section_payments": "Payment systems",
@@ -1039,6 +1040,7 @@
"admin_settings_subsection_heleket": "Heleket", "admin_settings_subsection_heleket": "Heleket",
"admin_settings_provider_webhook_url": "Webhook URL", "admin_settings_provider_webhook_url": "Webhook URL",
"admin_settings_provider_webhook_url_hint": "Use this URL in the provider webhook settings.", "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_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_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.", "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_label": "Remnawave API key",
"admin_settings_field_panel_api_key_description": "Secret API key for the panel.", "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_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_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_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", "admin_settings_field_user_external_squad_uuid_label": "Default External Squad",
+3 -1
View File
@@ -1017,6 +1017,7 @@
"admin_content": "Контент", "admin_content": "Контент",
"admin_logs_empty": "Записей нет", "admin_logs_empty": "Записей нет",
"admin_settings_section_general": "Общие", "admin_settings_section_general": "Общие",
"admin_settings_section_remnawave": "Remnawave Panel",
"admin_settings_section_appearance": "Внешний вид", "admin_settings_section_appearance": "Внешний вид",
"admin_settings_section_pricing": "Совместимость с legacy-тарифами", "admin_settings_section_pricing": "Совместимость с legacy-тарифами",
"admin_settings_section_payments": "Платёжные системы", "admin_settings_section_payments": "Платёжные системы",
@@ -1039,6 +1040,7 @@
"admin_settings_subsection_heleket": "Heleket", "admin_settings_subsection_heleket": "Heleket",
"admin_settings_provider_webhook_url": "Webhook URL", "admin_settings_provider_webhook_url": "Webhook URL",
"admin_settings_provider_webhook_url_hint": "Укажите этот адрес в настройках вебхуков провайдера.", "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_webhook_base_missing": "Укажите WEBHOOK_BASE_URL в .env, чтобы увидеть полный адрес для {path}.",
"admin_settings_provider_admin_only_label": "Только для админов", "admin_settings_provider_admin_only_label": "Только для админов",
"admin_settings_provider_admin_only_description": "Показывает провайдер только администраторам. Вебхуки и обработка статусов остаются активными для тестовых платежей.", "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_label": "API-ключ Remnawave",
"admin_settings_field_panel_api_key_description": "Секретный ключ API панели.", "admin_settings_field_panel_api_key_description": "Секретный ключ API панели.",
"admin_settings_field_panel_webhook_secret_label": "Секрет вебхуков Remnawave", "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_label": "Internal Squads по умолчанию",
"admin_settings_field_user_squad_uuids_description": "UUID через запятую для legacy-режима без JSON-каталога тарифов.", "admin_settings_field_user_squad_uuids_description": "UUID через запятую для legacy-режима без JSON-каталога тарифов.",
"admin_settings_field_user_external_squad_uuid_label": "External Squad по умолчанию", "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"] 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(): def test_payment_provider_admin_only_toggles_are_mutually_exclusive():
manifest = _manifest_by_key() manifest = _manifest_by_key()
+8
View File
@@ -760,10 +760,13 @@ class AdminSettingsSecurityTests(unittest.IsolatedAsyncioTestCase):
response = await admin_api.admin_settings_get_route(request) response = await admin_api.admin_settings_get_route(request)
payload = json.loads(response.text) payload = json.loads(response.text)
section_ids = [section["id"] for section in payload["sections"]]
fields = { fields = {
field["key"]: field for section in payload["sections"] for field in section["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( self.assertEqual(
fields["FREEKASSA_ENABLED"]["webhook_url"], fields["FREEKASSA_ENABLED"]["webhook_url"],
"https://web.tnnl.cc/webhook/freekassa", "https://web.tnnl.cc/webhook/freekassa",
@@ -773,6 +776,11 @@ class AdminSettingsSecurityTests(unittest.IsolatedAsyncioTestCase):
fields["PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_RU"]["webhook_url"], fields["PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_RU"]["webhook_url"],
"https://web.tnnl.cc/webhook/platega", "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"]) self.assertNotIn("webhook_url", fields["PAYMENT_STARS_WEBAPP_LABEL_RU"])