Template
Release v2.6.3: support page with SBP/card/crypto donate placeholders and admin settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -277,6 +277,12 @@ GitHub Actions workflows in `.github/workflows/`:
|
|||||||
|
|
||||||
## 📋 Fix / changelog (this fork)
|
## 📋 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
|
### 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.
|
* **Panel backup → PostgreSQL dump** — Settings backup downloads `.sql` via `pg_dump`; restore accepts `.sql` or legacy `data.json`. JSON export kept as secondary link.
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ else:
|
|||||||
application_path = os.path.dirname(__file__)
|
application_path = os.path.dirname(__file__)
|
||||||
|
|
||||||
DATA_FILE = os.path.join(application_path, 'data.json') # legacy JSON; used only for one-shot import / export
|
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_REPO_URL = repo_url()
|
||||||
RELEASES_API_LATEST = api_latest_url()
|
RELEASES_API_LATEST = api_latest_url()
|
||||||
BIN_DIR = os.environ.get('TUNNEL_BIN_DIR', os.path.join(application_path, 'bin'))
|
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):
|
def tpl(request, template, **kwargs):
|
||||||
data = load_data()
|
data = load_data()
|
||||||
lang = request.cookies.get('lang', 'en')
|
lang = request.cookies.get('lang', 'en')
|
||||||
|
donate_cfg = (data.get('settings') or {}).get('donate') or {}
|
||||||
ctx = {
|
ctx = {
|
||||||
'request': request,
|
'request': request,
|
||||||
'current_user': get_current_user(request, data),
|
'current_user': get_current_user(request, data),
|
||||||
'site_settings': data.get('settings', {}).get('appearance', {}),
|
'site_settings': data.get('settings', {}).get('appearance', {}),
|
||||||
'captcha_settings': data.get('settings', {}).get('captcha', {}),
|
'captcha_settings': data.get('settings', {}).get('captcha', {}),
|
||||||
'telegram_settings': data.get('settings', {}).get('telegram', {}),
|
'telegram_settings': data.get('settings', {}).get('telegram', {}),
|
||||||
|
'donate_enabled': bool(donate_cfg.get('enabled', True)),
|
||||||
'bot_running': tg_bot.is_running(),
|
'bot_running': tg_bot.is_running(),
|
||||||
'lang': lang,
|
'lang': lang,
|
||||||
'_': lambda text_id: _t(text_id, lang),
|
'_': lambda text_id: _t(text_id, lang),
|
||||||
@@ -2359,6 +2361,20 @@ class GuestSettings(BaseModel):
|
|||||||
create_xui_panel_id: str = ''
|
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):
|
class UpdateUserRequest(BaseModel):
|
||||||
telegramId: Optional[str] = None
|
telegramId: Optional[str] = None
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
@@ -2379,6 +2395,7 @@ class SaveSettingsRequest(BaseModel):
|
|||||||
telegram: TelegramSettings
|
telegram: TelegramSettings
|
||||||
ssl: SSLSettings
|
ssl: SSLSettings
|
||||||
guest: GuestSettings = GuestSettings()
|
guest: GuestSettings = GuestSettings()
|
||||||
|
donate: DonateSettings = DonateSettings()
|
||||||
|
|
||||||
|
|
||||||
class ToggleUserRequest(BaseModel):
|
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)
|
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"])
|
@app.get('/settings', response_class=HTMLResponse, tags=["System Templates"])
|
||||||
async def settings_page(request: Request):
|
async def settings_page(request: Request):
|
||||||
user = _check_admin(request)
|
user = _check_admin(request)
|
||||||
@@ -6381,6 +6410,7 @@ async def save_settings(request: Request, payload: SaveSettingsRequest):
|
|||||||
else:
|
else:
|
||||||
guest['password_hash'] = existing_guest.get('password_hash')
|
guest['password_hash'] = existing_guest.get('password_hash')
|
||||||
data['settings']['guest'] = guest
|
data['settings']['guest'] = guest
|
||||||
|
data['settings']['donate'] = payload.donate.dict()
|
||||||
|
|
||||||
save_data(data)
|
save_data(data)
|
||||||
logger.info("Settings saved (including captcha and telegram)")
|
logger.info("Settings saved (including captcha and telegram)")
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ DEFAULT_SETTINGS = {
|
|||||||
'create_protocol': 'awg',
|
'create_protocol': 'awg',
|
||||||
'create_server_id': 0,
|
'create_server_id': 0,
|
||||||
},
|
},
|
||||||
|
'donate': {
|
||||||
|
'enabled': True,
|
||||||
|
'intro': '',
|
||||||
|
'sbp': {'enabled': True, 'title': '', 'details': ''},
|
||||||
|
'card': {'enabled': True, 'title': '', 'details': ''},
|
||||||
|
'crypto': {'enabled': True, 'title': '', 'details': ''},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2207,6 +2207,15 @@ a:hover {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-link-support {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link-support:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: #f472b6;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-user {
|
.nav-user {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -108,4 +108,7 @@
|
|||||||
<symbol id="layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
<symbol id="layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>
|
<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="heart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.29 1.51 4.04 3 5.5l7 7Z"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -52,6 +52,9 @@
|
|||||||
<a href="/settings" class="nav-link">{{ icon('settings') }} {{ _('nav_settings') }}</a>
|
<a href="/settings" class="nav-link">{{ icon('settings') }} {{ _('nav_settings') }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/my" class="nav-link">{{ icon('layers') }} {{ _('nav_connections') }}</a>
|
<a href="/my" class="nav-link">{{ icon('layers') }} {{ _('nav_connections') }}</a>
|
||||||
|
{% if donate_enabled %}
|
||||||
|
<a href="/support" class="nav-link nav-link-support">{{ icon('heart') }} {{ _('nav_support') }}</a>
|
||||||
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="nav-user">
|
<div class="nav-user">
|
||||||
|
|||||||
+49
-1
@@ -650,6 +650,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- BLOCK: Support / Donate (admin) -->
|
||||||
|
{% set donate_cfg = settings.get('donate') or {} %}
|
||||||
|
<div class="card" style="margin-top: var(--space-lg);">
|
||||||
|
<h3 class="card-title" style="margin-bottom: var(--space-lg);">{{ icon('heart') }} {{ _('donate_settings_title') }}</h3>
|
||||||
|
<p class="form-hint" style="margin-top: 0; margin-bottom: var(--space-md);">{{ _('donate_settings_hint') }}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" style="display:flex; align-items:center; gap:8px; cursor:pointer;">
|
||||||
|
<input type="checkbox" id="donate_enabled" {% if donate_cfg.get('enabled', True) %}checked{% endif %}>
|
||||||
|
{{ _('donate_enabled') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ _('donate_intro') }}</label>
|
||||||
|
<textarea class="form-textarea" id="donate_intro" rows="2" placeholder="{{ _('support_intro') }}">{{ donate_cfg.get('intro') or '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
{% for key, label_key in [('sbp', 'donate_sbp'), ('card', 'donate_card'), ('crypto', 'donate_crypto')] %}
|
||||||
|
{% set method = donate_cfg.get(key) or {} %}
|
||||||
|
<div style="border:1px solid var(--border-color); border-radius:var(--radius-md); padding:var(--space-md); margin-bottom:var(--space-md);">
|
||||||
|
<label class="form-label" style="display:flex; align-items:center; gap:8px; cursor:pointer; margin-bottom:var(--space-sm);">
|
||||||
|
<input type="checkbox" id="donate_{{ key }}_enabled" {% if method.get('enabled', True) %}checked{% endif %}>
|
||||||
|
{{ _(label_key) }}
|
||||||
|
</label>
|
||||||
|
<div class="form-group" style="margin-bottom:var(--space-sm);">
|
||||||
|
<label class="form-label">{{ _('donate_method_title') }}</label>
|
||||||
|
<input type="text" class="form-input" id="donate_{{ key }}_title" value="{{ method.get('title') or '' }}" placeholder="{{ _(label_key) }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:0;">
|
||||||
|
<label class="form-label">{{ _('donate_method_details') }}</label>
|
||||||
|
<textarea class="form-textarea" id="donate_{{ key }}_details" rows="3" placeholder="{{ _('donate_details_placeholder') }}">{{ method.get('details') or '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<p class="form-hint">{{ _('donate_preview_hint') }} <a href="/support" target="_blank" rel="noopener">/support</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- BLOCK: About & Updates -->
|
<!-- BLOCK: About & Updates -->
|
||||||
<div class="card" style="margin-top: var(--space-lg);">
|
<div class="card" style="margin-top: var(--space-lg);">
|
||||||
<h3 class="card-title" style="margin-bottom: var(--space-lg);">ℹ️ {{ _('about_title') }}</h3>
|
<h3 class="card-title" style="margin-bottom: var(--space-lg);">ℹ️ {{ _('about_title') }}</h3>
|
||||||
@@ -1478,11 +1513,24 @@
|
|||||||
create_xui_panel_id: document.getElementById('guest_create_xui_panel')?.value || '',
|
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 {
|
try {
|
||||||
const res = await fetch('/api/settings/save', {
|
const res = await fetch('/api/settings/save', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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'));
|
if (!res.ok) throw new Error(_('error'));
|
||||||
showToast(_('settings_saved'), 'success');
|
showToast(_('settings_saved'), 'success');
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% from "macros/icons.html" import icon %}
|
||||||
|
|
||||||
|
{% block title_extra %} — {{ _('nav_support') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<style>
|
||||||
|
.support-hero {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-xl) var(--space-md);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
.support-hero-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.support-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
gap: var(--space-lg);
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.support-card {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
.support-card-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
.support-details {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px dashed var(--border-color);
|
||||||
|
min-height: 4rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.support-details.has-content {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="support-hero">
|
||||||
|
<div class="support-hero-icon">{{ icon('heart') }}</div>
|
||||||
|
<h1 class="section-title" style="justify-content: center; margin-bottom: var(--space-sm);">
|
||||||
|
{{ _('support_title') }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted" style="max-width: 560px; margin: 0 auto; line-height: 1.5;">
|
||||||
|
{% if donate.get('intro') %}
|
||||||
|
{{ donate.intro }}
|
||||||
|
{% else %}
|
||||||
|
{{ _('support_intro') }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="support-grid">
|
||||||
|
{% 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) %}
|
||||||
|
<div class="card support-card">
|
||||||
|
<div class="support-card-title">
|
||||||
|
<span aria-hidden="true">{{ emoji }}</span>
|
||||||
|
<span>{{ method.get('title') or default_title }}</span>
|
||||||
|
</div>
|
||||||
|
{% set details = (method.get('details') or '').strip() %}
|
||||||
|
<div class="support-details{% if details %} has-content{% endif %}" id="donate-{{ key }}-details">{{ details or _('donate_details_soon') }}</div>
|
||||||
|
{% if details %}
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" onclick="copyToClipboard(document.getElementById('donate-{{ key }}-details').textContent)">
|
||||||
|
{{ icon('copy') }} {{ _('copy') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-muted text-sm" style="text-align: center; margin-top: var(--space-xl); max-width: 520px; margin-left: auto; margin-right: auto;">
|
||||||
|
{{ _('support_footer') }}
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
"nav_invites": "Invite links",
|
"nav_invites": "Invite links",
|
||||||
"nav_settings": "Settings",
|
"nav_settings": "Settings",
|
||||||
"nav_connections": "Connections",
|
"nav_connections": "Connections",
|
||||||
|
"nav_support": "Support",
|
||||||
"nav_logout": "Logout",
|
"nav_logout": "Logout",
|
||||||
"theme_dark": "Dark",
|
"theme_dark": "Dark",
|
||||||
"theme_light": "Light",
|
"theme_light": "Light",
|
||||||
@@ -405,6 +406,21 @@
|
|||||||
"server_connect_domain_ssh": "SSH",
|
"server_connect_domain_ssh": "SSH",
|
||||||
"server_connect_domain_saved": "Connection domain saved",
|
"server_connect_domain_saved": "Connection domain saved",
|
||||||
"server_endpoint_label": "Endpoint",
|
"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",
|
"container_logs": "Container logs",
|
||||||
"logs_btn": "📋 Logs",
|
"logs_btn": "📋 Logs",
|
||||||
"logs_live": "Live",
|
"logs_live": "Live",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"nav_invites": "Ссылки",
|
"nav_invites": "Ссылки",
|
||||||
"nav_settings": "Настройки",
|
"nav_settings": "Настройки",
|
||||||
"nav_connections": "Подключения",
|
"nav_connections": "Подключения",
|
||||||
|
"nav_support": "Поддержать",
|
||||||
"nav_logout": "Выход",
|
"nav_logout": "Выход",
|
||||||
"theme_dark": "Темная",
|
"theme_dark": "Темная",
|
||||||
"theme_light": "Светлая",
|
"theme_light": "Светлая",
|
||||||
@@ -405,6 +406,21 @@
|
|||||||
"server_connect_domain_ssh": "SSH",
|
"server_connect_domain_ssh": "SSH",
|
||||||
"server_connect_domain_saved": "Домен подключения сохранён",
|
"server_connect_domain_saved": "Домен подключения сохранён",
|
||||||
"server_endpoint_label": "Endpoint",
|
"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": "Логи контейнера",
|
"container_logs": "Логи контейнера",
|
||||||
"logs_btn": "📋 Логи",
|
"logs_btn": "📋 Логи",
|
||||||
"logs_live": "В реальном времени",
|
"logs_live": "В реальном времени",
|
||||||
|
|||||||
Reference in New Issue
Block a user