Commit Graph
68 Commits
Author SHA1 Message Date
leonidorlov-hash a1a6a70426 ui(rknblock): 2x2 action grid, drop base-updated line; .server-icon white color 2026-10-20 00:55:51 +03:00
leonidorlov-hash 341876016a feat(rknblock): per-direction options, base-updated stamp, rainbow icon
- new card button 'Options' + modal with 4 flags: block/log inbound/outbound
- new endpoint POST /api/servers/{id}/rknblock/options driving the blocker
  'set' subcommand; record merge in install/enable keeps flags across toggles
- status probe head 6 -> 10; parser reads 'flags:' line and 'last_update:'
- card summary shows base update time and disabled directions
- _rkn_run_cmd now refreshes the host-side script copy before running
- icon shield -> rainbow in card and marketplace app list
- translations: 14 new keys x 5 languages
2026-10-20 00:55:51 +03:00
leonidorlov-hash 4a74e2127f feat(rknblock): install from marketplace runs the shared progress log
Clicking Install no longer drops back to the page with a bare toast:
the shared installModal opens in progress-only mode (form hidden) with
the progress bar and a running step log (download scripts -> ipset sets
-> firewall rules -> scan agent), finishing with the green Done state
exactly like protocol installs.
2026-10-20 00:55:51 +03:00
leonidorlov-hash b69a2ae6fc feat(rknblock): red scan-alert badge opens a full scans popup
The alert badge in the card now stands out with a red fill and a
'details' label; clicking it opens a large modal with the per-org and
per-port breakdown, direction totals and the recent-event table (all
previously dumped inline into the card). The card itself keeps only the
short agent/lists summary; the clear-statistics button moved into the
modal footer.
2026-10-20 00:55:51 +03:00
leonidorlov-hash 008e9fa260 feat(rknblock): RKN/VK blocking as a marketplace app with lifecycle buttons
Replace the raw rkn_block_enabled toggle with a full marketplace app:
- install/uninstall/enable/update endpoints; uninstall fully removes the
  contour (rules, ipset sets, units, scripts, configs) via a fresh
  rkn-extra-block copy so old servers can always be cleaned
- server page card with Start/Stop/Delete/Update buttons, scan count in
  the badge row, stats detail in the card info
- servers-overview scan alert now anchors to the marketplace card
- legacy rkn_block_enabled flag dropped in load_data; rkn_scans response
  gains installed/enabled
- amnezia-blocker repo gains 'rkn-extra-block uninstall' (e7514a9)
2026-10-20 00:55:51 +03:00
leonidorlov-hash f0b830cd29 feat(install): survive a network abort during long protocol installs
A from-scratch Xray build keeps the install request busy for ~6 min;
a browser-to-panel connection loss (proxy/VPN timeout) surfaces as a
bare 'Failed to fetch' and looks like a failed install even though
the server side completed fine. On a network abort, poll /check for
the new protocol and report success when it shows up.
2026-10-13 23:41:58 +03:00
leonidorlov-hash 133f27fdd3 feat(settings): duckdns card — free domain + one-click auto-renewed cert
New settings card 'Your own duckdns.org domain' next to SSL settings:
- 3-step mini guide, domain + token fields (token never echoed back,
  empty field keeps the stored one)
- POST /api/settings/duckdns/apply: normalizes the domain, points it at
  this server's public IP (empty ip= -> duckdns uses the caller), then
  issues a Let's Encrypt cert via acme.sh DNS-01 (dns_duckdns plugin,
  port 80 not required) and installs it to /etc/amnezia/
- the card drives the SAME settings['ssl'] config as the manual card
- acme.sh cron renews automatically; under systemd --reloadcmd restarts
  the panel, and the first apply restarts it too (2s timer after the
  response) so HTTPS comes up on its own
- background monitor keeps the duckdns IP fresh every 600s
- 5 locales, 11 new tests (484 total OK)
2026-10-03 15:08:03 +03:00
Максимов Владимир Сергеевич 553f41926d Security, Fixes and localization
Security:
- reject existing sessions for disabled and record-only accounts
- enforce protocol and instance ownership when downloading configurations
- store CAPTCHA answers server-side with expiry and replay protection
- revoke share authorization after password changes or sharing is disabled
- restrict language-switch redirects and harden language cookies
- limit JSON backup size and improve input validation
- quote Telemt command arguments and SSH upload paths
- use private temporary directories for sudo operations and backup downloads
- selectively update dependencies to address confirmed vulnerabilities

Fixes and localization:
- fix service worker and static asset version paths in PyInstaller builds
- complete French, Persian, and Chinese translations to match all 687 keys
- translate remaining English strings in those locales
- add security, bundled-resource, and translation completeness regression tests
- add English and Russian security reports documenting fixes and remaining risks
2026-09-28 17:53:25 +03:00
Public relations & Virtual technology 40ebb74186 Merge pull request #183 from Almeonamy/fix/awg-mtu-budget
fix(awg): size the default client MTU against per-packet obfuscation
2026-09-28 16:28:01 +03:00
Public relations & Virtual technology 938054c2bc Merge branch 'main' into feat/instance-public-address 2026-09-28 16:17:00 +03:00
Алексей ЦыганковandClaude Opus 5 0b834c0cc6 fix(awg): size the default client MTU against per-packet obfuscation
AWG_DEFAULTS['mtu'] is a fixed 1376 for every protocol generation, but
AWG 3.x appends a random ContentPaddingAddition to every data packet on
top of S4. With the parameters this panel generates, a fresh 3.x install
produces worst-case packets of 1504 bytes against a plain 1500-byte link.

ContentPaddingAddition is a range, so only the packets drawing its upper
end are oversized: handshakes succeed and small requests work while bulk
transfers stall, which reads as "flaky with some sites" rather than as a
broken tunnel.

Derive the default instead. transport_overhead() counts what obfuscation
costs per data packet (60-byte header stack + S4 + the top of
ContentPaddingAddition; handshake sizes and junk packets travel in their
own packets and are excluded). safe_client_mtu() turns that into the
largest MTU whose worst case still fits, floored at 1280 per RFC 8200.

install_protocol uses it when no MTU was passed; an explicit value from
the UI still wins, since operators behind a provider that tunnels their
traffic need to go lower still. The AWG settings modal now warns when the
entered MTU cannot fit a standard link, and the install modal no longer
hardcodes 1376.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:02:33 +07:00
AlmeonamyandClaude Opus 5 959ec78e3e feat(protocols): per-instance public address for client endpoints
Every config and link the panel issues carried the address it opens SSH
to. That is not always where clients can reach the instance: a second IP
on the same box, a port forward, a domain name. Telemt made it worse --
it prints its own tg:// links, and install rewrote public_host in
config.toml with the panel's address, so a manually bound instance
handed out links to the wrong endpoint after every reinstall.

An instance record now carries optional public_host/public_port, set
from the globe button on its card or via
POST /api/servers/{id}/protocol/public-endpoint. The address flows into
AmneziaWG/WireGuard Endpoints, vless:// and tg:// links, connections
created for a user and self-service provisioning. Unset means exactly
what it meant before: the server address and the instance's listen port.

- telemt: the address is written into config.toml and reloaded without a
  restart; saving with the box down keeps the record and warns
- telemt: _patch_public_links matches the keys line by line -- the old
  pattern could span lines and weld public_host onto the line above
- xray: an explicit public port wins, but meta.json (synced from the
  live server.json) still beats the panel's remembered port
- reinstalling an instance keeps the address, like its exit link

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 14:08:54 +07:00
leonidorlov-hash edd6f2f640 feat(servers): opt-in live peer refresh interval (default off)
The connections list used to poll every 5s unconditionally. Each poll is a
full connections read over SSH - fine on fast servers, heavy on slow/flaky
ones. The management modal now offers a per-server interval selector:
0 (off, default), 5, 10, 15, 20, 45, 120, 300, 600 seconds. Stored as
server['peer_poll_interval'], validated server-side against the step list,
i18n for all five locales.
2026-09-14 14:39:53 +03:00
Максимов Владимир Сергеевич cb0a5db3b5 Merge PR #174: nonblocking SSH and circuit breaker
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:40:26 +03:00
Максимов Владимир Сергеевич 64d8ad76ae Merge PR #170: peer editor user linking
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:22 +03:00
Максимов Владимир Сергеевич 1a36478e28 Merge PR #168: WireGuard peer discovery and translations
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:11 +03:00
Максимов Владимир Сергеевич 826f6d4692 Merge PR #167: multi-select connection linking
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:01 +03:00
Максимов Владимир Сергеевич 80adfece00 Merge PR #166: unlink connections and peer deep links
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:38:53 +03:00
leonidorlov-hash 1248ce68c0 feat(servers): configurable SSH circuit-breaker base cooldown
Per-server setting ssh_cooldown_base (default 30s, range 5-300) for the
SSH circuit breaker introduced with the event-loop fix. Editable in the
server management modal with a 'leave it alone when healthy' hint, and
shown in the host tuning modal. get_ssh applies edits to the pooled
connection without dropping it.
2026-09-13 23:48:10 +03:00
leonidorlov-hash 6d4800aaac fix(ui): action tooltips on the bulb; editor never force-links a user
- Bulb tooltip used the state key ('Stopped'); added
  stop_peer_title/start_peer_title action keys in all 5 locales.
- The user select defaulted to the first no-role user, so saving a
  rename silently linked the peer. '-- do not link --' now comes first
  and is selected unless the peer is already linked.
2026-09-13 21:19:53 +03:00
leonidorlov-hash 182abe65bf feat(server): link/unlink a user right from the peer rename editor 2026-09-13 20:15:48 +03:00
leonidorlov-hash 6ccff31436 feat(users): multi-select linking, remembered options, no full page reload
- Link-existing flow now supports selecting several peers at once
  (Ctrl); each link is applied independently with a summary toast,
  partial failures do not abort the rest.
- Add-connection modal remembers the last used mode/server/protocol
  (localStorage) and restores them on next open.
- Linking or creating a connection refreshes the users grid via AJAX
  and live-updates an open connections modal instead of reloading the
  whole page.
- Server-side guard against double-linking a peer: a peer already
  assigned to any panel user is rejected with a message naming the
  current owner (a peer is a single on/off entity on the server;
  sharing it between users made both cards control the same peer).
- Linking an existing peer no longer pops up the config modal.
- Translations for all 5 locales.
2026-09-13 20:05:38 +03:00
leonidorlov-hash 110b3e4882 feat(users): unlink-without-delete action + deep links to peers
- POST /api/users/{id}/connections/unlink: detaches a key from the user
  without deleting the peer on the server (admin-only, 404 if not found)
- users page: scissors button per linked connection, server/instance
  names open the server page in a new tab and highlight the peer row
- server page: ?protocol=&highlight= deep link handling, peer row
  gets .peer-highlight CSS class and scrolls into view
- translations for all 5 locales
2026-09-13 19:54:56 +03:00
leonidorlov-hash 155a0673dc fix(users): WireGuard existing-clients list + missing i18n keys
- api_get_server_clients called manager.get_clients(protocol) directly,
  but WireGuardManager.get_clients() takes no protocol argument, so the
  link-existing dropdown failed with a TypeError. Route through
  _manager_call like every other callsite.
- Add translation keys referenced by templates but absent from all
  language files (select_connection, name_required,
  no_servers_available) — the UI fell back to raw key names.
2026-09-13 15:59:32 +03:00
leonidorlov-hash b5284b8feb feat(awg): editable IPv6 DNS field in install modal and AWG settings 2026-09-13 14:44:27 +03:00
hell 828425ebc9 fix(users): localise the invalid-role error too
Left over from the same handler: 'Invalid role' answered in English next to
_t('user_exists') and _t('password_required_for_role'). New key invalid_role
in all five translation files.
2026-09-10 00:17:23 +07:00
hell 2ed0bc6ab5 fix(users): initialise the password label, and localise the empty-username error
- The init call for toggleNewUserPasswordReq() had landed in the ucProtocol
  change handler, which is unrelated to the add-user modal: the label still
  said "required" for the default role "none" until something touched that
  dropdown. It runs on DOMContentLoaded now.
- api_update_user answered a hard-coded English "Username must not be empty"
  next to _t('user_exists'), so a Russian UI got one toast in each language.
  New key username_empty in all five translation files.
2026-09-10 00:17:22 +07:00
Public relations & Virtual technology f66ba9b71d Merge branch 'main' into feat/users-norole 2026-09-09 12:18:05 +03:00
leonidorlov-hash 54e735953b Users: editable username, optional password with 'no role' default
- Edit user modal now allows renaming (unique, non-empty; enforced
  server-side in /api/users/{id}/update).
- Add user form: password is optional; new default role 'none' — a
  record-only user that cannot log in and needs no password. Picking
  any real role (user/support/admin) makes the password required,
  validated both client-side and server-side.
- Login skips users without a password hash.
- Role badge shows a translated 'No role' label; new translation keys
  added for ru/en/fa/fr/zh.
2026-09-08 23:17:09 +03:00
hellandClaude Opus 5 69893ba340 feat(exit): route client traffic through another panel server
Today a client leaves to the internet from the same server it connected to.
Exit nodes split the two: clients connect to an *entry* node (reachable,
possibly behind DPI) and egress from an *exit* node (clean IP, other country).
One exit serves many entries and keeps serving its own AWG clients.

    client --AWG--> [entry] amnezia-awg2 --WG/AWG link--> [exit] amnezia-exit --> internet
                    awg0 10.8.1.0/24                     transit0 10.9.0.0/24
                    exit0 10.9.0.N (Table=off)           one [Peer] per entry, /32

Exit side: a new protocol `exit` = container `amnezia-exit`, an AmneziaWG
listener on 55520/udp over the transit subnet 10.9.0.0/24. Peers are panel
nodes, never end users, and live in their own peersTable. ExitManager
subclasses AWGManager and closes every client-facing method.

Entry side: a second interface `exit0` inside the existing AWG container with
`Table = off`, plus policy routing - `from <client subnet> lookup 200`,
`default dev exit0` in table 200, and `blackhole default metric 1000` as a
kill-switch installed *before* the client tunnel comes up, so a container
restart cannot leak. Clients are MASQUERADEd into the entry's transit /32, so
the identical 10.8.1.0/24 of every instance can never collide at the exit.
TCP MSS is clamped, and rules 190/191 keep client<->client and AmneziaDNS
traffic local. State is file-driven: exit0.conf present means linked, and the
same shell block runs at container start and on a live (un)link.

Link crypto is WireGuard; AWG obfuscation is a per-exit-node property for hops
that cross DPI, and S3/S4 are deliberately not written on the transit - they
pad every transport packet and would eat the MTU budget of a tunnel inside a
tunnel. Private keys are generated inside the containers; the panel carries
public keys and a per-peer PSK only.

exit_link_service.py orchestrates linking with per-(entry, protocol) locks,
SSH off the event loop, a 15-second handshake gate that rolls back, and
lifecycle hooks for every way a link can go stale: exit uninstalled, stopped,
deleted, host cleared, entry reinstalled, container gone, backup restored.
Servers get a stable `uid` (already upstream) so reorder and delete cannot
break a link.

IPv4 only for now: while linked, client IPv6 is refused with `unreachable`
rather than leaking out of the entry.

Also included: an egress check that curls a public IP service bound to the
tunnel gateway (the client's exact path) and compares it with the direct one;
optional DNS resolution at the exit node; a default exit node applied to new
AWG installs; and reconciliation after a protocol restore.

244 tests pass. CURRENT_VERSION is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 23:05:51 +07:00
Public relations & Virtual technology eb403b1a2e Merge branch 'main' into feat/wgeasy-import-upstream 2026-09-08 13:10:15 +03:00
Public relations & Virtual technology e70c822503 Merge branch 'main' into feat/protocol-logos 2026-09-08 12:56:28 +03:00
leonidorlov-hash 266f23d743 feat(ui): protocol logos and custom instance names
- WireGuard, AmneziaWG (all variants) and Xray cards show real logos
  (bundled in static/img) instead of emoji, on brand-colored tiles:
  WireGuard #871719, AmneziaWG #303030, Xray #010001
- installed instances can be renamed via a small pencil inside the
  instance name (fades in on hover); the custom name is stored per
  protocol in the server record (custom_name) and shown on the card
  title and in all instance pickers; empty input restores the default
2026-09-05 17:23:59 +03:00
Viktor Baranov 30156c5829 feat(ui): design system and mobile layout overhaul 2026-09-05 16:55:33 +03:00
leonidorlov-hash 41351067a0 feat(wgeasy-import): auto-target AWG 2.0 when source has obfuscation
w0rng/amnezia-wg-easy sources carry AmneziaWG obfuscation params
(Jc/Jmin/Jmax/S1-S4/H1-H4) in the [Interface] section. Importing such
clients into a plain WireGuard instance would break their configs, so
the importer now reads the source wg0.conf from the container, detects
the obfuscation and installs an AWG 2.0 instance with the exact same
params instead. Plain wg-easy sources still land on WireGuard.

Preview now reports the detected listen port, obfuscation presence and
the resolved target protocol.
2026-09-05 02:55:14 +03:00
leonidorlov-hash eb12bf138b feat: import clients from wg-easy / amnezia-wg-easy panels
Adds an 'Import from wg-easy' button on the server page. The panel
fetches the client list from a running wg-easy (v14 / w0rng fork) or
wg-easy v15 instance through the server's own SSH connection (web API
on 127.0.0.1, nothing needs to be exposed), shows a preview with
selectable clients, and on confirmation:

- stops the old wg-easy container (not deleted)
- installs the panel-managed WireGuard instance on the same UDP port
- restores the original server key, subnet and all client keys/PSKs/IPs,
  so existing client configs keep working unchanged
- writes a backup copy of wg0.json to /root on the server

The WG container start script now accepts a custom subnet so NAT rules
match the imported network instead of the panel default 10.8.2.0/24.
2026-09-05 02:55:14 +03:00
Viktor Baranov 3770870e32 feat: add ability to restore server backups 2026-09-04 13:39:56 +03:00
Public relations & Virtual technology 2dbaf78048 Merge branch 'main' into feature/self-service-upstream 2026-09-01 18:23:47 +03:00
Максимов Владимир Сергеевич 6b937b463b Merge pull request #108 from leonidorlov-hash/feat/install-used-port-awareness
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-01 17:23:38 +03:00
Максимов Владимир Сергеевич cc56be4317 Merge pull request #106 from leonidorlov-hash/feat/peer-speed-limit
# Conflicts:
#	templates/server.html
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-01 17:22:30 +03:00
Public relations & Virtual technology 9bf44a75c3 Merge branch 'main' into feat/awg-special-junk-mtu-dns 2026-09-01 16:20:05 +03:00
Public relations & Virtual technology 317e5d7ad6 Merge pull request #105 from Almeonamy/feat/searchable-user-select
Add substring search to the assign-to-user picker
2026-09-01 16:06:38 +03:00
Public relations & Virtual technology 929b293811 Merge branch 'main' into feat/net-tuning 2026-09-01 15:48:27 +03:00
hellandClaude Opus 5 92bcf7bb9e Name and package Xray connections the way AmneziaVPN reads them
Three separate problems, none of them new to this branch.

The vless link carried the connection's own label in its fragment, and
vless::Deserialize hands that fragment to extractXrayConfig as the server
description -- so an imported key showed up as "Connection". It now carries
the same node-plus-container name the AmneziaWG configs got.

The `vpn://` key was base64 of the vless link, which the client cannot read:
extractConfigFromData only routes a *bare* `vless://` string to the Xray
branch, and once it is wrapped nothing recognises the payload. That is where
the ErrorCode 900 (ImportInvalidConfigError) came from, and it predates this
branch -- main wraps every protocol the same way.

The QR had the same shape of problem from the other side:
extractConfigFromQr only reaches extractConfigFromData when checkConfigFormat
already recognises the text, and a bare vless link matches none of its
patterns, so a link in a QR code is unreachable however well it scans.

So Xray now goes through the same Amnezia config format as AmneziaWG:
build_xray_client_config ports serialization::vless::Deserialize, wraps its
output the way extractXrayConfig would, and the result feeds both the `vpn://`
key and the QR frame series. Four frames for a typical reality link.

The plain-format QR is still there for v2rayNG and friends, so its label is no
longer "WireGuard".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 14:46:45 +07:00
hellandClaude Opus 5 af87995c87 Fix the protocol card header and make the QR scannable
Two problems with the previous commit's UI.

The header row of a protocol card is a bare space-between flex with no gap,
so the third control button ran straight into the protocol icon on the AWG
cards -- the only ones that get an AWG button. Give the row a gap and let the
button group wrap instead of pushing into the icon.

And the QR: a whole config in one code lands at version 19 raw or 26 wrapped,
while the Android scanner builds ImageAnalysis.Builder() with no
ResolutionSelector and so reads 640x480 frames -- two to three pixels per
module. Emit the frame series ImportController::parseQrCodeChunk reassembles
instead: qint16 magic 1984, quint8 total, quint8 index and the slice as a
QDataStream QByteArray, each frame base64url'd into its own code. At 144 bytes
a frame every code stays under 55x55 modules and decoded at every scale tried
against a simulated VGA capture, including ones where the single code failed.

The WireGuard format stays a single static code, since a raw config is what
AmneziaWG and wg-quick read and it cannot be chunked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 02:37:45 +07:00
hellandClaude Opus 5 1fbcba5c48 Name the server in the vpn:// key and QR code
A plain WireGuard config cannot carry a name: extractWireGuardConfig() in
the client overwrites description with nextAvailableServerName(), so every
imported key lands as "Server 1". Wrap the config into Amnezia's own server
config format instead, which keeps whatever description it is given, and
name it after the node and the container it came from -- "prx-40 AWG3".

The encoding matches ExportController::generateVpnUrl: base64url of
qCompress'd JSON, the size prefix included. The QR payload carries no
vpn:// prefix, because extractConfigFromQr() feeds the scanned text
straight to QByteArray::fromBase64, which drops ':' and '/' but keeps
'v', 'p' and 'n' and would shift the whole payload by three characters.

AmneziaWG and wg-quick only understand the raw config, so the QR panel
gets a format switch rather than replacing what it shows. The named
payload is about half again as long -- QR version 23 instead of 18 --
so it is drawn a little larger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 02:09:31 +07:00
leonidorlov-hash 3847a5667d feat(install): used-port awareness when installing protocols
Previously the install modal defaulted to a fixed port and only avoided
ports of same-family instances, so installing e.g. AWG3 on a host where
AWG2 already held 55424 failed mid-install with a half-created broken
container ('port is already allocated').

- GET /api/servers/{id}/used-ports: real listening UDP/TCP ports via ss
- api_install_protocol: reject a busy port upfront with a clear error
  naming the owning process, before any docker run
- Install modal: fetches used ports, auto-suggests the first free port,
  live-validates manual input (red field + hint + disabled Install button)
- new translation key port_in_use_by (en/ru/fr/fa/zh)
2026-08-29 04:41:06 +03:00
leonidorlov-hash 58e976121f feat(awg): trash button to clear a peer's connection-flood warnings
The torrent-detection warning block under a peer now has a 🗑 button that
wipes the recorded warning history for that peer (conn_warnings.json is
keyed by peer IP; backend resolves the IP from clientsTable).
Adds POST /api/servers/{id}/connections/clear_warnings and
clear_warnings / warnings_cleared translation keys.
2026-08-29 01:12:31 +03:00
leonidorlov-hash c59ccd36e2 feat: per-peer speed limit (Mbps, step 0.1, 0=unlimited) for AmneziaWG
- awg_manager: set_speed_limit() applies tc HTB (egress) + ingress policer
  inside container for peer IPv4/IPv6; limits persisted to bwlimits file and
  re-applied on container start
- maxSpeed stored in clientsTable userData
- UI: speed badge on peer card, speed field with label in rename dialog
- translations: en/ru/fr/fa/zh
2026-08-29 01:10:41 +03:00
Алексей ЦыганковandClaude Opus 5 133dc2bff6 Add substring search to the "assign to user" picker
On a panel with a few dozen users, picking one when creating a connection
means scrolling a plain <select> and reading every entry — there is no way
to type a name.

Adds a small dependency-free searchable select and applies it to the user
picker in the Add Connection modal. It's progressive enhancement: the
native <select> stays in the DOM as the source of truth, so `.value`,
programmatic assignment and `change` listeners keep working exactly as
before (server.html reads `connectionUserId.value` unchanged).

- static/js/searchable-select.js: opt-in via `data-searchable`, with
  case-insensitive substring filtering, keyboard navigation
  (arrows/Enter/Escape/Tab), an empty state, and ARIA combobox roles
- styles built entirely from existing CSS variables, so light/dark themes
  and RTL follow the rest of the panel
- labels come from `data-search-*` attributes, so strings stay in the
  templates; new keys added to all five locales

No jQuery/select2: the panel ships no jQuery today, and pulling in two
libraries for one dropdown seemed a poor trade against ~300 lines of
vanilla JS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 18:20:15 +07:00