RUST-FEAT-036 - Settings UI v2¶
Summary¶
Make the Rust daemon settings surface beta-ready without turning it into a legacy preference mirror. The canonical model is:
--profile <dir>selects the profile directory.<dir>/emulebb-rust-settings.tomlis bootstrap-only and owns REST bind/auth.<dir>/emulebb-rust-metadata.dbis the SQLite profile repository.emulebb-settingsowns typed settings DTOs, defaults, and validation helpers./api/v1/app/settingsexposes DB-backed daemon settings.
There are no compatibility aliases, old-name remaps, schema migrations, or version bumps for development-phase cleanup. If an operator-local profile must be preserved during development, update that local SQLite file explicitly.
Product Rule¶
Do not split the product into hidden preferences/config/settings surfaces.
- User-facing daemon settings live in SQLite and are exposed through
/api/v1/app/settings. - REST bind/auth stays in the fixed bootstrap TOML because the REST server needs it before it can serve the settings API.
- Existing resources such as shared directories, categories, servers, and Kad operations are settings sections in the UI, not duplicate setting keys.
- The UI should use regular, explicit controls. Reuse
emulebb-settingsvalidation/default metadata where it is already available or simple to add, but do not block the UI on a generic metadata renderer. - Protocol parity remains mandatory for eD2K/Kad behavior; local REST/UI shape should stay Rust-native and clean.
Current DB-Backed Settings¶
AppSettings currently exposes:
core: bandwidth, connection budgets, upload queue policy, server behavior, protocol toggles, and credit-system behavior.daemon: incoming directory, P2P bind IP/interface, and eD2K user hash.ed2k: listen port, obfuscation, timeouts, reconnect policy, source budgets, upload queue tuning, download limit, UDP reask, identity advertisement, server import behavior, and dead-server retry budget.kad: listen port, local-store behavior, publishing, firewall checks, buddy, routing maintenance, and snoop-queue tuning.nat: UPnP/NAT behavior.vpnGuard: VPN guard mode and allow-list.ipFilter: IP filter enable/path/level.
Missing Beta-Facing Controls¶
Expose these before beta through regular Settings UI controls or existing section resources:
- Downloads/storage:
incomingDirand related path validation. - Sharing: manage shared folder roots as a primary section. Every root is a monitored folder tree; do not expose single-file sharing or a non-recursive sharing mode.
- Categories and category paths: already supported by REST; manage with clear path semantics.
- eD2K listen port and Kad listen port: settings that require restart or explicit reconnect where live apply is not available.
- P2P bind IP/interface: network safety setting, restart-required unless live rebind is implemented.
- VPN Guard: enabled, mode, allowed public CIDRs, and current verdict/status.
- IP filter: enabled, path, level, reload/status.
- REST bind/API key: bootstrap security section, restart-required, backed by the fixed profile TOML.
- Server repository/import: existing server routes as a clear settings section backed by SQLite.
- Kad bootstrap nodes/import: existing Kad routes as a clear settings section.
Advanced Settings¶
Expose only behind an advanced section if they are surfaced for beta:
- Kad shared-file publishing toggle.
- Kad republish interval.
- Kad UDP/TCP firewall check toggles and intervals.
- Kad buddy and routing-maintenance toggles.
- Kad local-store capacities/TTLs.
- Kad snoop-queue tuning.
- eD2K dead server retries.
- Server keepalive/session rotation.
- UDP source reask toggle.
- NAT/UPnP backend settings.
Profile-Only / Not Normal UI¶
Do not put these in the normal Settings UI for beta:
- The profile directory itself.
- The fixed profile file names.
- Internal transfer/piece-store paths.
- Low-level Kad index capacities unless an explicit diagnostics mode needs them.
- Diagnostic probe terms and parity-harness knobs.
- Identity spoof/advertising toggles.
Execution Plan¶
- Settings inventory
- Keep a machine-readable inventory of every beta-facing setting and classify it as normal control, advanced control, existing section resource, bootstrap-only, or not-user-facing.
-
Add a policy/test guard so beta-facing fields cannot be forgotten when
DaemonProfile,AppSettings,Ed2kSettings,KadSettings,VpnGuardSettings, orIpFilterSettingschanges. -
REST settings contract
- Keep
/api/v1/app/settingsas the canonical DB-backed settings resource. - Keep OpenAPI aligned with implementation in the same change.
-
Do not document or keep unimplemented schema routes.
-
Settings section routing
- Treat existing routes as settings sections:
/shared-directories,/categories,/servers,/kad, and diagnostics or status endpoints where relevant. -
Avoid duplicating those resources inside
AppSettings. -
Regular UI controls
- Bind explicit UI controls to
AppSettingsand existing section routes. - Add dirty state, inline validation, save/revert, and restart-required indicators.
-
Add an advanced toggle and keep advanced controls out of the normal flow.
-
Beta settings coverage
- Add Settings UI sections for downloads/storage, sharing, categories, servers, Kad, network/binding, VPN Guard, IP filter, REST/security, and advanced tuning.
-
Every visible control must call a real API or clearly show that restart is required. No inert fields.
-
Validation
emulebb-settingstests cover defaults, field names, bounds, and update validation.- REST route tests prove invalid fields and invalid values fail from shared validators.
- UI compile/tests prove every visible settings section renders.
- OpenAPI conformance includes
/api/v1/app/settingsand the section resources used by the UI.
Acceptance Criteria¶
- [ ] All beta-facing settings above are represented in
AppSettings, bootstrap TOML, or an existing section resource. - [ ] No beta-facing setting is hidden only in TOML except REST bind/auth.
- [ ] UI uses regular controls backed by real APIs.
- [ ] Restart-required settings are explicit and cannot be mistaken for live-applied settings.
- [ ] Existing settings-adjacent resources are reachable from the Settings UI.
- [ ] No inert compatibility fields or legacy preference names are introduced.
- [ ] REST OpenAPI matches implementation and test coverage.
Implementation Notes¶
- 2026-07-18: Added scan-friendly Diagnostics summary metrics in the embedded WebUI for process, file counts, shared hashing/reload progress, upload counts, and eD2K/Kad publish phases while preserving the raw runtime JSON panel for deep inspection.
- 2026-07-18: Marked shadowed
ed2k.safeServerConnectanded2k.addServersFromServersettings surface entries as not-user-facing, with a guard test. The visible Settings controls remain the canonicalcore.*server toggles used by runtime state. - 2026-07-18: Added an advanced WebUI Search settings section for existing
eD2K keyword, exact-hash keyword, and source server attempt budgets. The
controls PATCH the existing
ed2ksettings object and stay hidden until Advanced is enabled. - 2026-07-18: Added advanced WebUI controls for existing eD2K network budget
settings: new outgoing connections per five seconds and half-open connection
cap. Labels intentionally distinguish these protocol-level caps from the
higher-level
coreconnection controls. - 2026-07-18: Added advanced WebUI controls for existing eD2K transfer tuning:
session rotation, concurrent download cap, eD2K source cap, parallel download
peer cap, and startup download byte budget. These PATCH the existing
ed2ksettings section and keep labels distinct from the higher-levelcorecaps. - 2026-07-18: Added an advanced WebUI Uploads section for existing
ed2k.uploadQueuetuning settings: startup slots, elasticity, byte budgets, underfill thresholds, waiting/granted/upload timeouts, session transfer target, and session time limit. The controls PATCH the nesteded2k.uploadQueuesettings object. - 2026-07-18: Added advanced WebUI controls for existing Kad tuning settings: bootstrap contact floor, local-store enable, publish contact fanout, and UDP/TCP firewall-check intervals. Low-level Kad store capacities, TTLs, and snoop queue fields remain not-user-facing per the settings surface inventory.
- 2026-07-18: Added advanced WebUI controls for existing NAT tuning settings:
pinned IGD IP, miniSSDPd socket, SSDP local port, discovery timeout, lease
duration, renew margin, and external IP override. These stay behind the
Advanced toggle and PATCH the existing
natsettings section. - 2026-07-18: Added advanced WebUI controls for existing eD2K timeout and retry
settings: peer connect timeout, server connect timeout, callback timeout,
reconnect interval, keepalive interval, and dead-server retries. These stay
behind the Advanced toggle and PATCH the existing
ed2ksettings section. - 2026-07-18: Reworked the WebUI Settings tab from a flat control grid into
domain sections for Storage, Transfers, Network, Hostname Lookup, Servers, Kad,
NAT, VPN Guard, and IP Filter. Each section still binds to real
AppSettingsfields and keeps metadata-driven advanced visibility, restart badges, validation, and save/revert behavior. - 2026-07-18: Added inline WebUI validation for Settings numeric and listen-port
controls. Invalid whole-number/range values now render field-level errors and
disable Save before malformed
PATCH /api/v1/app/settingsrequests can be sent; the settings e2e covers the invalid-port path. - 2026-07-18: Added Settings-tab navigation for the existing section resources
advertised by
GET /api/v1/app/settings/surface, making Sharing, Categories, Servers, Kad, and Diagnostics reachable from Settings without duplicating those resources insideAppSettings. WebUI e2e coverage now proves the section-resource path opens a real section. - 2026-07-18: Wired the embedded WebUI to
GET /api/v1/app/settings/surfaceand used the metadata in the Settings view for advanced-control hiding, restart-required badges, dirty Save/Revert state, and e2e coverage of the operator flow. - 2026-07-18: Exposed the DB-backed settings inventory through
GET /api/v1/app/settings/surface. The route returns classified/app/settingsfield metadata and existing settings-section resources while keeping bootstrap-only TOML secrets out of REST. Rust route metadata, REST tests, and OpenAPI are aligned. - 2026-07-18: Added a machine-readable settings surface inventory in
emulebb-settingsfor all serializedAppSettingsfields, plus existing settings-section resources. Added a daemon bootstrap TOML inventory for the REST bind/auth/WebUI-root fields. Tests now fail if either serialized settings field set grows without a classification. - 2026-07-18: Added a first-class
GET /api/v1/diagnosticspower-user section resource. It exposes the sameRuntimeDiagnosticsobject embedded inGET /status, with OpenAPI, route metadata, REST tests, WebUI model/refresh wiring, mock API coverage, and Diagnostics tab rendering aligned in the same slice. - 2026-07-18: Added static OpenAPI drift coverage for destructive confirmation
sentinels so operator actions such as shutdown, diagnostics capture/crash,
log clearing, transfer clearing, and shared-root replacement remain documented
as required explicit
trueconfirmations. - 2026-07-18: Tightened the embedded WebUI
AppSettingsAPI model from loose records to typed section shapes for core, daemon, eD2K, Kad, NAT, VPN Guard, and IP Filter settings. The Settings controls still use the same REST contract, but TypeScript now tracks the first-class fields that the UI edits. - 2026-07-18: Added the IP Filter settings section resource. The Rust REST
surface now exposes
GET /api/v1/ip-filterfor configured/reloadable status, level, and loaded range count, plusPOST /api/v1/ip-filter/operations/reloadto re-read the configuredipfilter.datinto the live shared filter. The Settings UI shows the runtime status and reload action next to the IP Filter controls, with OpenAPI and route/body validation aligned. - 2026-07-18: Added redacted bootstrap REST/security inventory to
GET /api/v1/app/settings/surfaceasbootstrapSettings. The Settings UI now shows therest.bindAddr,rest.apiKey, andrest.webRootDirTOML-owned fields as restart-required Bootstrap REST entries without exposing secret values or making them mutable through REST. - 2026-07-18: Tightened storage path validation for the beta Settings surface.
PATCH /api/v1/app/settingsnow rejects emptyincomingDirvalues before persistence and rejects existing non-directory incoming paths at the core boundary; shared-directory root replacement canonicalizes through the same long-path content boundary used by shared-tree scanning. OpenAPI documentsincomingDiras a non-empty nullable path string. - 2026-07-18: Added the VPN Guard settings section resource. The Rust REST
surface now exposes
GET /api/v1/vpn-guardfor startup block state, egress verification, public IP, and bound STUN/HTTP probe outcomes; Settings renders the live verdict beside the existing VPN Guard controls without duplicating status insideAppSettings. - 2026-07-18: Added the Network settings section resource. The Rust REST surface
now exposes
GET /api/v1/networkfor P2P port, bind-resolution, active interface, and nested VPN Guard status; Settings renders the live bind verdict beside restart-required P2P bind controls instead of duplicating runtime network status insideAppSettings. - 2026-07-18: Added the NAT settings section resource. The Rust REST surface now
exposes
GET /api/v1/natfor gateway discovery, active mappings, observed external addresses, refresh time, and last error; Settings renders live mapping health beside restart-required NAT controls without duplicating manager state insideAppSettings. - 2026-07-18: Tightened Settings section-resource reachability. In-page section resources such as NAT, VPN Guard, and IP Filter now focus their concrete Settings sections when opened from the advertised section-resource list, and the WebUI smoke covers those resource-button paths before navigating to Diagnostics.
- 2026-07-18: Added REST coverage proving every Settings section resource
advertised by
GET /api/v1/app/settings/surfaceresolves to an authenticated live REST v1 GET route and returns adataenvelope, so future section resources cannot remain UI-only or docs-only links. - 2026-07-18: Extended the Rust OpenAPI static route checker to parse
SETTINGS_SECTION_RESOURCESfromemulebb-settingsand fail when an advertised Settings section resource is not documented as a GET operation in the Rust OpenAPI artifact. - 2026-07-18: Tightened Settings section-resource OpenAPI response shapes. The
static checker now rejects generic or inline section-resource success schemas
and requires each advertised Settings section resource to expose a named
closed
dataDTO; category, shared-directory, and server collection envelopes now reference namedCategoryList,SharedDirectoryList, andServerListDTO schemas without changing the wire shape. - 2026-07-18: Added a VPN Guard power-user probe operation. The Rust REST
surface now exposes
POST /api/v1/vpn-guard/operations/probe, which runs the bound dual-plane egress probe when the runtime has a probeable VPN Guard CIDR gate and returns the refreshedVpnGuardStatusenvelope. The Settings UI now offers the same probe action beside the VPN Guard controls. - 2026-07-18: Added a NAT power-user refresh operation. The Rust REST surface
now exposes
POST /api/v1/nat/operations/refresh, which asks the live NAT manager for one reconcile pass and returns the refreshedNatStatusenvelope. Mapping failures stay diagnostic by landing inlastError, so the Settings UI can show the live gateway/mapping result without treating a missing IGD as a transport-level API failure. - 2026-07-18: Added Logs to the advertised Settings section resources. The
existing
GET /api/v1/logsandPOST /api/v1/logs/operations/clearoperator surface is now discoverable fromGET /api/v1/app/settings/surface, and the Settings UI opens the existing Logs tab instead of duplicating retained log state insideAppSettings. - 2026-07-18: Added
GET /api/v1/events/statusas an adapter-friendly live event-stream diagnostics resource. Controllers can now inspect SSE capacity, subscriber count, queued events, and cursor state without opening the long-lived stream or fetching full runtime diagnostics; capabilities advertise this astransfers.sse.status. - 2026-07-18: Tightened
PATCH /api/v1/app/settingssemantics to be true field-level partial updates for every non-core settings section. Power-user scripts can now change one NAT, Kad, eD2K, daemon, VPN Guard, or IP Filter field without accidentally restoring omitted fields to defaults; explicit JSONnullstill clears nullable fields. OpenAPI now documents named*SettingsUpdaterequest DTOs separately from full settings response DTOs. - 2026-07-18: Aligned the embedded WebUI with the partial settings PATCH
contract. The Settings save path now builds sparse
AppSettingsUpdatepayloads from changed form fields only, and no longer sends the shadowed not-user-facing eD2K server toggles when saving the canonicalcore.*controls. WebUI e2e coverage asserts the exact sparse payload. - 2026-07-18: Tightened REST body validation for the partial settings PATCH
contract. Empty top-level settings updates, empty section update objects, and
empty nested update objects such as
daemon.hostnameLookupanded2k.uploadQueuenow fail before the handler, matching the OpenAPIminPropertiescontract. - 2026-07-18: Tightened REST body validation for non-core partial settings
updates. Unknown fields inside
daemon,ed2k,kad,nat,vpnGuard,ipFilter, and nested update objects now fail in the route-body layer with deterministicunknown settings.<path> fielderrors, matching the OpenAPI closed-object contract before serde or handler parsing. - 2026-07-18: Tightened hostname lookup settings update validation. The REST
body layer now rejects
daemon.hostnameLookup.cacheTtlSecsandmaxLookupsPerTickvalues below1, plustickIntervalSecsvalues below5, matching the OpenAPI minima before settings are persisted. - 2026-07-18: Tightened VPN Guard mode as a finite Rust-native settings enum.
The default DB-backed setting now uses
off, OpenAPI documentsoffandblock, REST PATCH rejects other mode strings before persistence, and the embedded WebUI renders the mode as a selector instead of a free text field. - 2026-07-18: Tightened Kad settings update validation for values that the
runtime previously clamped after persistence. REST PATCH and OpenAPI now
require
bootstrapMinRoutingContacts,republishIntervalSecs, andpublishContactFanoutto be at least1, and Kad UDP/TCP firewall check intervals to be at least60seconds. The embedded Settings UI uses the same floors before saving. - 2026-07-18: Tightened configured port settings so REST PATCH and OpenAPI reject
0for eD2K listen, Kad listen, and NAT SSDP local ports while preserving JSONnullas the explicit clear value. The Settings UI already used the same1..65535range. - 2026-07-18: Tightened the API-only Kad local-store and snoop-queue controls so
REST PATCH and OpenAPI reject
0for TTL, capacity, rate-budget, cooldown, deduplication, and stop-after-results fields. These values were already clamped to1by daemon construction; the contract now rejects ineffective writes before persistence. - 2026-07-18: Aligned the embedded Settings UI with documented eD2K zero-policy
controls. Advanced users can now save
0for eD2K keepalive, concurrent download cap, new-connection rate cap, half-open cap, and per-file source cap, matching the REST/OpenAPI contract and runtime semantics for disabled or uncapped limits. - 2026-07-18: Tightened eD2K min-one peer/search budgets. REST PATCH and OpenAPI
now reject
0formaxParallelDownloadPeers,keywordServerAttemptBudget,exactHashKeywordServerAttemptBudget, andsourceServerAttemptBudget, matching the runtime paths that already force those values to at least one effective attempt or peer. - 2026-07-18: Tightened
ed2k.deadServerRetriesto the stock retry range. REST PATCH, OpenAPI, and the embedded Settings UI now require1..10, matching the eD2K runtime contract for non-static server removal after consecutive connect or ping failures. - 2026-07-18: Tightened eD2K upload-queue settings around the runtime-effective bounds. REST PATCH and OpenAPI now reject clamped-away startup slot, elasticity, underfill, timeout, and session-transfer values, while the Settings UI now preserves zero-policy controls for an empty retained waiting queue, disabled session-transfer rotation, and disabled session-time rotation.
- 2026-07-18: Tightened NAT backend-order settings around the Rust-native backend
set. REST PATCH, OpenAPI, and the embedded Settings UI now accept only
upnp_miniupnpcentries while preserving an empty order as the runtime default order. - 2026-07-18: Tightened NAT timing settings so REST PATCH and OpenAPI reject
0for discovery timeout, lease duration, and renew margin. This matches the embedded Settings UI and prevents persisted values that the UPnP runtime clamps away during discovery or refresh scheduling. - 2026-07-18: Tightened NAT address settings as nullable IPv4 strings. REST
PATCH, OpenAPI, and the embedded Settings UI now reject malformed
nat.bindIp,nat.igdIp, andnat.externalIpOverridevalues before the UPnP adapter has to reinterpret or fail them later. - 2026-07-18: Tightened
daemon.p2pBindIpPATCH handling to match its typed settings DTO and existing OpenAPIipv4format. REST route-body validation and the embedded Settings UI now reject malformed bind-IP text before persistence. - 2026-07-18: Tightened
daemon.ed2kUserHashas a canonical eD2K identity override. REST PATCH and OpenAPI now require a marker-normalized 32-character lowercase hex user hash, preservingnullas the explicit clear value and rejecting values the daemon would otherwise normalize or reject at network bootstrap. - 2026-07-18: Tightened nullable text/path settings that previously accepted
ineffective empty strings. REST PATCH and OpenAPI now require non-empty
daemon.p2pBindInterface,nat.minissdpdSocket, andipFilter.pathvalues when present, while preservingnullas the explicit clear value. - 2026-07-18: Tightened
vpnGuard.allowedPublicIpCidrsto the core VPN Guard CIDR policy. REST PATCH, OpenAPI, and the embedded Settings UI now accept an empty string as "no CIDR gate" or a whitespace/comma/semicolon-separated list of public IPv4 CIDRs or host addresses; malformed, IPv6, and non-public ranges are rejected before persistence. - 2026-07-18: Aligned the
NatStatus.ssdpLocalPortresponse schema with the nullable configured-port contract. OpenAPI now advertisesnullor1..65535instead of allowing an impossible configured port0. - 2026-07-18: Aligned the bulk transfer-add request schema with the REST link
validator. OpenAPI now advertises the existing
linksceiling of 100 eD2K links per request instead of only requiring a non-empty array. - 2026-07-18: Tightened WebUI transfer-add link validation to match the REST and
OpenAPI batch contract. The Transfers view now blocks non-eD2K, whitespace,
overlong, and over-100-link batches before sending
POST /api/v1/transfers. - 2026-07-18: Tightened WebUI validation for core numeric settings to match the
shared REST/OpenAPI core settings schema maxima. The Settings UI now rejects
out-of-range power-user values such as
core.maxConnectionsbefore sending a PATCH that the daemon would refuse. - 2026-07-18: Aligned the category priority OpenAPI schema with the REST
validator and Rust model. Numeric category priorities and category ids now
advertise the
u32ceiling instead of only documenting the lower bound. - 2026-07-18: Tightened WebUI section-resource operation port validation.
Server add and Kad bootstrap forms now enforce
1..65535before sending requests, matching the REST/OpenAPI request contracts for those existing section resources. - 2026-07-18: Tightened WebUI section-resource URL import validation. Server
list and Kad nodes import forms now require HTTP(S) URLs with a host before
sending requests, matching the shared
UrlImportRequestREST/OpenAPI contract. - 2026-07-18: Tightened WebUI friend-create validation. The Friends view now
requires canonical lowercase eD2K user hashes and bounded display names before
sending
POST /api/v1/friends, matching the REST/OpenAPI request contract. - 2026-07-18: Tightened WebUI category validation. Category create and row-edit
controls now block empty names and unsupported priority values before sending
POSTorPATCHrequests, while preserving the REST/OpenAPI string priority names and power-user numericu32priority path. - 2026-07-18: Aligned WebUI search creation with the Rust-native REST search
type tokens. The Search view now sends
"",arc,doc,iso,image,pro,audio,video, oremulecollectioninstead of unsupported friendly aliases, and normalizes/validates query text beforePOST /api/v1/searches. - 2026-07-18: Tightened WebUI shared-file metadata validation. The Shared Files
editor now offers only REST/OpenAPI upload-priority tokens and blocks ratings
outside
0..5before sendingPATCH /api/v1/shared-files/{hash}. - 2026-07-18: Tightened WebUI shared-folder root validation. The Sharing view
now blocks empty or whitespace-only folder paths before sending
PATCH /api/v1/shared-directories, matching the REST/OpenAPI shared-root replacement contract. - 2026-07-18: Tightened WebUI endpoint-address validation. Server add and Kad bootstrap forms now require non-empty trimmed addresses before sending requests, matching the REST/OpenAPI request contracts alongside the existing port checks.
- 2026-07-14:
RUST-FEAT-036 keep ED2K servers in SQLite profileremoved normal daemon TOML server ownership. Enabled SQLite profile servers decide whether an ED2K server session can be configured. - 2026-07-14: Profile naming was consolidated around
--profile,emulebb-rust-settings.toml,emulebb-rust-metadata.db,/api/v1/app/settings, andemulebb-settings.
Next Implementation Slice¶
Start with the backend/UI inventory:
- classify every current
AppSettingsand bootstrap TOML field; - decide normal vs advanced vs restart-required controls;
- test it against OpenAPI and the UI;
- then fill the missing Settings UI sections.