From 32a0e90e193cca60e40f3e0b1afb022bfbc420ca Mon Sep 17 00:00:00 2001 From: orohi Date: Tue, 28 Jul 2026 13:32:09 +0300 Subject: [PATCH] Release v2.6.3: support page with SBP/card/crypto donate placeholders and admin settings. Co-authored-by: Cursor --- README.md | 6 +++ app.py | 32 ++++++++++++- db/store.py | 7 +++ static/css/style.css | 9 ++++ static/icons.svg | 3 ++ templates/base.html | 3 ++ templates/settings.html | 50 ++++++++++++++++++- templates/support.html | 104 ++++++++++++++++++++++++++++++++++++++++ translations/en.json | 16 +++++++ translations/ru.json | 16 +++++++ 10 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 templates/support.html diff --git a/README.md b/README.md index f0f501f..358700f 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,12 @@ GitHub Actions workflows in `.github/workflows/`: ## 📋 Fix / changelog (this fork) +### v2.6.3 +* **Client connection domain (AWG / WireGuard)** — set a DNS name per server (or per AWG/WG protocol) instead of IP in client configs (`Endpoint = domain:port`). Survives server IP changes — update the A-record only. UI on server list, server page, and when adding a server. +* **Legacy `data.json` → PostgreSQL** — import normalizes old backups (UUIDs, duplicate usernames/tokens, string `server_info`); failed auto-import no longer bricks panel startup; JSON restore returns clear errors. +* **Dokploy / Traefik 404** — `traefik.http.services.amnezia_panel.loadbalancer.server.port=5000` in compose (Traefik must route to container port **5000**). +* **Support the project** — `/support` page for all users (SBP / card / crypto placeholders); admin configures requisites in **Settings** (no popups). + ### v2.6.2 * **Panel backup → PostgreSQL dump** — Settings backup downloads `.sql` via `pg_dump`; restore accepts `.sql` or legacy `data.json`. JSON export kept as secondary link. diff --git a/app.py b/app.py index c0f4bb5..2c26e36 100644 --- a/app.py +++ b/app.py @@ -103,7 +103,7 @@ else: application_path = os.path.dirname(__file__) DATA_FILE = os.path.join(application_path, 'data.json') # legacy JSON; used only for one-shot import / export -CURRENT_VERSION = "v2.6.5" +CURRENT_VERSION = "v2.6.3" RELEASES_REPO_URL = repo_url() RELEASES_API_LATEST = api_latest_url() BIN_DIR = os.environ.get('TUNNEL_BIN_DIR', os.path.join(application_path, 'bin')) @@ -241,12 +241,14 @@ def get_current_user(request: Request, data: Optional[dict] = None): def tpl(request, template, **kwargs): data = load_data() lang = request.cookies.get('lang', 'en') + donate_cfg = (data.get('settings') or {}).get('donate') or {} ctx = { 'request': request, 'current_user': get_current_user(request, data), 'site_settings': data.get('settings', {}).get('appearance', {}), 'captcha_settings': data.get('settings', {}).get('captcha', {}), 'telegram_settings': data.get('settings', {}).get('telegram', {}), + 'donate_enabled': bool(donate_cfg.get('enabled', True)), 'bot_running': tg_bot.is_running(), 'lang': lang, '_': lambda text_id: _t(text_id, lang), @@ -2359,6 +2361,20 @@ class GuestSettings(BaseModel): create_xui_panel_id: str = '' +class DonateMethodSettings(BaseModel): + enabled: bool = True + title: str = '' + details: str = '' + + +class DonateSettings(BaseModel): + enabled: bool = True + intro: str = '' + sbp: DonateMethodSettings = DonateMethodSettings() + card: DonateMethodSettings = DonateMethodSettings() + crypto: DonateMethodSettings = DonateMethodSettings() + + class UpdateUserRequest(BaseModel): telegramId: Optional[str] = None email: Optional[str] = None @@ -2379,6 +2395,7 @@ class SaveSettingsRequest(BaseModel): telegram: TelegramSettings ssl: SSLSettings guest: GuestSettings = GuestSettings() + donate: DonateSettings = DonateSettings() class ToggleUserRequest(BaseModel): @@ -6072,6 +6089,18 @@ async def api_my_connection_config(request: Request, connection_id: str): return JSONResponse({'error': str(e)}, status_code=500) +@app.get('/support', response_class=HTMLResponse, tags=["System Templates"]) +async def support_page(request: Request): + user = get_current_user(request) + if not user: + return RedirectResponse('/login', status_code=302) + data = load_data() + donate = dict((data.get('settings') or {}).get('donate') or {}) + if not donate.get('enabled', True): + return RedirectResponse('/my', status_code=302) + return tpl(request, 'support.html', donate=donate) + + @app.get('/settings', response_class=HTMLResponse, tags=["System Templates"]) async def settings_page(request: Request): user = _check_admin(request) @@ -6381,6 +6410,7 @@ async def save_settings(request: Request, payload: SaveSettingsRequest): else: guest['password_hash'] = existing_guest.get('password_hash') data['settings']['guest'] = guest + data['settings']['donate'] = payload.donate.dict() save_data(data) logger.info("Settings saved (including captcha and telegram)") diff --git a/db/store.py b/db/store.py index e206f08..d3f7f45 100644 --- a/db/store.py +++ b/db/store.py @@ -54,6 +54,13 @@ DEFAULT_SETTINGS = { 'create_protocol': 'awg', 'create_server_id': 0, }, + 'donate': { + 'enabled': True, + 'intro': '', + 'sbp': {'enabled': True, 'title': '', 'details': ''}, + 'card': {'enabled': True, 'title': '', 'details': ''}, + 'crypto': {'enabled': True, 'title': '', 'details': ''}, + }, } diff --git a/static/css/style.css b/static/css/style.css index 65aa2d2..b999b08 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2207,6 +2207,15 @@ a:hover { font-weight: 600; } +.nav-link-support { + opacity: 0.85; +} + +.nav-link-support:hover { + opacity: 1; + color: #f472b6; +} + .nav-user { display: flex; align-items: center; diff --git a/static/icons.svg b/static/icons.svg index 3cc7afd..98c620e 100644 --- a/static/icons.svg +++ b/static/icons.svg @@ -108,4 +108,7 @@ + + + diff --git a/templates/base.html b/templates/base.html index 4e44204..13e95c2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -52,6 +52,9 @@ {{ icon('settings') }} {{ _('nav_settings') }} {% endif %} {{ icon('layers') }} {{ _('nav_connections') }} + {% if donate_enabled %} + {{ icon('heart') }} {{ _('nav_support') }} + {% endif %} + + {% set donate_cfg = settings.get('donate') or {} %} +
+

{{ icon('heart') }} {{ _('donate_settings_title') }}

+

{{ _('donate_settings_hint') }}

+
+ +
+
+ + +
+ {% for key, label_key in [('sbp', 'donate_sbp'), ('card', 'donate_card'), ('crypto', 'donate_crypto')] %} + {% set method = donate_cfg.get(key) or {} %} +
+ +
+ + +
+
+ + +
+
+ {% endfor %} +

{{ _('donate_preview_hint') }} /support

+
+

ℹ️ {{ _('about_title') }}

@@ -1478,11 +1513,24 @@ create_xui_panel_id: document.getElementById('guest_create_xui_panel')?.value || '', }; + const donateMethod = (key) => ({ + enabled: document.getElementById(`donate_${key}_enabled`).checked, + title: document.getElementById(`donate_${key}_title`).value.trim(), + details: document.getElementById(`donate_${key}_details`).value.trim(), + }); + const donate = { + enabled: document.getElementById('donate_enabled').checked, + intro: document.getElementById('donate_intro').value.trim(), + sbp: donateMethod('sbp'), + card: donateMethod('card'), + crypto: donateMethod('crypto'), + }; + try { const res = await fetch('/api/settings/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ appearance, sync, captcha, telegram, ssl, guest }) + body: JSON.stringify({ appearance, sync, captcha, telegram, ssl, guest, donate }) }); if (!res.ok) throw new Error(_('error')); showToast(_('settings_saved'), 'success'); diff --git a/templates/support.html b/templates/support.html new file mode 100644 index 0000000..b07ab7e --- /dev/null +++ b/templates/support.html @@ -0,0 +1,104 @@ +{% extends "base.html" %} +{% from "macros/icons.html" import icon %} + +{% block title_extra %} — {{ _('nav_support') }}{% endblock %} + +{% block head_extra %} + +{% endblock %} + +{% block content %} +
+
{{ icon('heart') }}
+

+ {{ _('support_title') }} +

+

+ {% if donate.get('intro') %} + {{ donate.intro }} + {% else %} + {{ _('support_intro') }} + {% endif %} +

+
+ +
+ {% set methods = [ + ('sbp', _('donate_sbp'), '💳'), + ('card', _('donate_card'), '🏦'), + ('crypto', _('donate_crypto'), '₿'), + ] %} + {% for key, default_title, emoji in methods %} + {% set method = donate.get(key) or {} %} + {% if method.get('enabled', True) %} +
+
+ + {{ method.get('title') or default_title }} +
+ {% set details = (method.get('details') or '').strip() %} + + {% if details %} + + {% endif %} +
+ {% endif %} + {% endfor %} +
+ +

+ {{ _('support_footer') }} +

+{% endblock %} diff --git a/translations/en.json b/translations/en.json index c70935d..eb00922 100644 --- a/translations/en.json +++ b/translations/en.json @@ -4,6 +4,7 @@ "nav_invites": "Invite links", "nav_settings": "Settings", "nav_connections": "Connections", + "nav_support": "Support", "nav_logout": "Logout", "theme_dark": "Dark", "theme_light": "Light", @@ -405,6 +406,21 @@ "server_connect_domain_ssh": "SSH", "server_connect_domain_saved": "Connection domain saved", "server_endpoint_label": "Endpoint", + "support_title": "Support the project", + "support_intro": "If this panel helps you, you can support development. Choose a convenient method below.", + "support_footer": "Thank you for using Amnezia Web Panel.", + "donate_sbp": "SBP (Fast Payments)", + "donate_card": "Bank card", + "donate_crypto": "Cryptocurrency", + "donate_details_soon": "Payment details will be added soon.", + "donate_settings_title": "Support / donations", + "donate_settings_hint": "Shown on the /support page for all users. No popups — only when they open the page.", + "donate_enabled": "Show support page in navigation", + "donate_intro": "Page intro text (optional)", + "donate_method_title": "Button title (optional)", + "donate_method_details": "Payment details", + "donate_details_placeholder": "Phone, card number, wallet address…", + "donate_preview_hint": "Preview:", "container_logs": "Container logs", "logs_btn": "📋 Logs", "logs_live": "Live", diff --git a/translations/ru.json b/translations/ru.json index 87399f1..06d8bc3 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -4,6 +4,7 @@ "nav_invites": "Ссылки", "nav_settings": "Настройки", "nav_connections": "Подключения", + "nav_support": "Поддержать", "nav_logout": "Выход", "theme_dark": "Темная", "theme_light": "Светлая", @@ -405,6 +406,21 @@ "server_connect_domain_ssh": "SSH", "server_connect_domain_saved": "Домен подключения сохранён", "server_endpoint_label": "Endpoint", + "support_title": "Поддержать проект", + "support_intro": "Если панель вам помогает, можно поддержать разработку. Выберите удобный способ ниже.", + "support_footer": "Спасибо, что пользуетесь Amnezia Web Panel.", + "donate_sbp": "СБП", + "donate_card": "Банковская карта", + "donate_crypto": "Криптовалюта", + "donate_details_soon": "Реквизиты будут добавлены позже.", + "donate_settings_title": "Поддержка / донат", + "donate_settings_hint": "Страница /support для всех пользователей. Без всплывающих окон — только по ссылке в меню.", + "donate_enabled": "Показывать страницу поддержки в меню", + "donate_intro": "Текст в начале страницы (необязательно)", + "donate_method_title": "Название кнопки (необязательно)", + "donate_method_details": "Реквизиты", + "donate_details_placeholder": "Телефон, номер карты, адрес кошелька…", + "donate_preview_hint": "Предпросмотр:", "container_logs": "Логи контейнера", "logs_btn": "📋 Логи", "logs_live": "В реальном времени",