docs: TODO + CLAUDE.md notes + locale keys for new features

TODO.md grows the device-support follow-up roadmap. CLAUDE.md trims a
stale section. en/ru/zh locales add the strings used by the new
HTTP-endpoint editor, MiniSelect labels, automations expansion, and
value-source kinds. Ru/zh parity for the older keys is tracked
separately in REVIEW_TODO.md.
This commit is contained in:
2026-05-23 00:50:31 +03:00
parent ddae5719cf
commit fd46c51dba
5 changed files with 436 additions and 23 deletions
-4
View File
@@ -55,10 +55,6 @@ The Android app (`android/app/build.gradle.kts`) installs the server package wit
| [Gitea Python CI/CD Guide](https://git.dolgolyov-family.by/alexei.dolgolyov/claude-code-facts/src/branch/main/gitea-python-ci-cd.md) | Reusable CI/CD patterns: Gitea Actions, cross-build, NSIS, Docker | | [Gitea Python CI/CD Guide](https://git.dolgolyov-family.by/alexei.dolgolyov/claude-code-facts/src/branch/main/gitea-python-ci-cd.md) | Reusable CI/CD patterns: Gitea Actions, cross-build, NSIS, Docker |
| [server/CLAUDE.md](server/CLAUDE.md) | Backend architecture, API patterns, common tasks | | [server/CLAUDE.md](server/CLAUDE.md) | Backend architecture, API patterns, common tasks |
## Task Tracking via TODO.md
Use `TODO.md` in the project root as the primary task tracker. **Do NOT use the TodoWrite tool** — all progress tracking goes through `TODO.md`.
## Documentation Lookup ## Documentation Lookup
**Use context7 MCP tools for library/framework documentation lookups** (FastAPI, OpenCV, Pydantic, yt-dlp, etc.) instead of relying on potentially outdated training data. **Use context7 MCP tools for library/framework documentation lookups** (FastAPI, OpenCV, Pydantic, yt-dlp, etc.) instead of relying on potentially outdated training data.
+165
View File
@@ -1,5 +1,170 @@
# LedGrab TODO # LedGrab TODO
## HTTP polling automation trigger
Goal: a new automation trigger that periodically polls an HTTP endpoint
and activates a scene when the response matches a condition. Split into
three single-responsibility entities so the endpoint can be reused
beyond automations (e.g. as a value-source driving brightness/color):
- `HTTPEndpoint` (storage/http_endpoint.py) — connection definition:
URL + auth + headers + timeout. NO polling cadence; NO extraction.
- `HTTPValueSource` (storage/value_source.py, source_type='http') —
references an endpoint + owns json_path + interval + min/max + EMA
smoothing. Backed by `HTTPValueStream` (core/processing/value_stream.py)
which lives under the existing `ValueStreamManager` (ref-counted,
one poll task per unique value source).
- `HTTPPollRule` (storage/automation.py) — thin: `{value_source_id,
operator, value}`. Reads `stream.get_raw_value()` from the value
source and compares with `_apply_operator`.
Pivoted from a 2-entity shape mid-build (was: HTTPSource+rule with
interval+json_path mushed). The 3-entity shape mirrors HA's pattern
(HomeAssistantSource → HAEntityValueSource → rule).
### Phase 1 — endpoint + value source + thin rule (backend) ✅
- [x] `storage/http_endpoint.py` — `HTTPEndpoint` dataclass with
secret_box auth_token encryption + `__post_init__` plaintext
invariant. NO `default_interval_s` (moved to value source).
- [x] `storage/http_endpoint_store.py` — `HTTPEndpointStore` with
`_migrate_plaintext_tokens()`. ID prefix `htep_`.
- [x] `storage/database.py` — `"http_endpoints"` in `_ENTITY_TABLES`
(replaces the old `"http_sources"`).
- [x] `storage/value_source.py` — added `HTTPValueSource` alongside
`HAEntityValueSource` (endpoint_id, json_path, interval_s,
min/max, smoothing). Registered in `_VALUE_SOURCE_MAP`.
- [x] `storage/value_source_store.py` — CRUD branch for `source_type =
"http"` + new kwargs on create/update.
- [x] `core/processing/value_stream.py` — `HTTPValueStream` with poll
task + `get_value()` (normalized 0-1) + `get_raw_value()` (raw
extracted value). Dispatched in `ValueStreamManager._create_stream`.
Manager now takes `http_endpoint_store` so the stream can resolve
endpoints at fetch time.
### Phase 2 — rule + engine wiring ✅
- [x] `storage/automation.py` — `HTTPPollRule` is now thin: just
`{value_source_id, operator, value}` (no http_source_id, no
json_path on the rule). Legacy keys silently dropped on load.
- [x] `core/automations/automation_engine.py` — drops the standalone
http_poll_manager; takes `value_stream_manager`. Engine
`_sync_value_stream_refs` acquires/releases value streams for
every enabled HTTPPollRule, mirroring the HA/MQTT sync pattern.
`_evaluate_http_poll` reads `stream.get_raw_value()` and applies
the operator. `_apply_operator` kept at module top.
- [x] `api/schemas/automations.py` — RuleSchema fields are now
`value_source_id + operator + value` (dropped http_source_id +
json_path).
- [x] `api/routes/automations.py` — `http_poll` factory updated.
### Phase 3 — CRUD endpoints + wiring ✅
- [x] `api/schemas/http_endpoints.py` — Create/Update/Response/List/Test
(no interval field; that's on the value source).
- [x] `api/routes/http_endpoints.py` — full CRUD + `/test` +
plaintext-http-token warning.
- [x] `api/schemas/value_sources.py` — `HTTPValueSource{Create,Update,Response}`
added to the discriminated unions.
- [x] `api/routes/value_sources.py` — `_RESPONSE_MAP` entry for
`HTTPValueSource`.
- [x] `api/__init__.py` — `http_endpoints_router` registered.
- [x] `api/dependencies.py` — `get_http_endpoint_store` (dropped the
http_poll_manager getter).
- [x] `main.py` — instantiate `HTTPEndpointStore`, pass it through
`ProcessorDependencies`, wire `value_stream_manager` +
`value_source_store` into `AutomationEngine`.
- [x] `core/processing/processor_manager.py` — `ProcessorDependencies`
gains `http_endpoint_store`; threaded into `ValueStreamManager`.
### Phase 4 — tests ✅
- [x] `tests/storage/test_http_endpoint_store.py` — 14 tests (CRUD +
auth_token encryption + headers + case-insensitive Authorization).
- [x] `tests/core/test_automation_engine.py` — `TestApplyOperator` +
`TestHTTPPollRuleEvaluation` (new shape: mock ValueStreamManager
with `_streams` dict) + `TestSyncValueStreamRefs` (acquire /
release / disabled-ignored) + `TestHTTPValueStreamExtraction`
(`_extract_simple_path` now lives in value_stream.py).
- [x] `tests/api/routes/test_http_endpoints_routes.py` — CRUD shape, no
auth_token leak in responses, schema-layer method allowlist,
CRLF / invalid header rejection, `/test` endpoint, LAN policy.
- [x] Removed: `tests/core/test_http_poll_manager.py` (manager deleted —
polling now lives inside `HTTPValueStream`).
- [x] Full suite: 1426 passed, ruff clean.
### Phase 5 — frontend ✅
- [x] `static/js/features/http-endpoints.ts` (new, ~540 LOC) — endpoint
CRUD, modal subclass with dirty-check, headers row editor, test
result rendering, card builder, event delegation. Mirrors
`home-assistant-sources.ts`.
- [x] `templates/modals/http-endpoint-editor.html` (new) — sectioned
rack-panel modal (Identity / Request / Headers / Notes) with
IconSelect method picker, password-toggle on auth token, inline
Test button + result block.
- [x] `static/js/features/value-sources.ts` — added `http` branch with
EntitySelect over `httpEndpointsCache`, edit-data/defaults,
`onValueSourceTypeChange` section toggle, save-payload assembly
+ required-field validation.
- [x] `templates/modals/value-source-editor.html` — new
`#value-source-http-section` with endpoint picker + json_path +
interval + min/max + smoothing.
- [x] `static/js/features/automations.ts` — `http_poll` rule type with
operator IconSelect + value-source EntitySelect; hides Value
field when operator is `exists`.
- [x] `static/js/features/integrations.ts` — `csHTTPEndpoints` section,
tree/tab entry, render + reconcile + delegation paths.
- [x] `static/js/types.ts` — `HTTPEndpoint`, `HTTPMethod`,
`HTTPEndpointListResponse`, `HTTPTestRequest/Response`,
`HTTPValueSource`, `HTTPPollOperator`; extended `RuleType` +
`AutomationRule`.
- [x] `static/js/core/state.ts` — `httpEndpointsCache` (`/http/endpoints`).
- [x] `static/js/core/icons.ts` — `http: P.globe` in
`_valueSourceTypeIcons`.
- [x] `templates/index.html` — includes
`modals/http-endpoint-editor.html`.
- [x] Locales: 77 new keys per file in `en.json` / `ru.json` /
`zh.json` (parity confirmed).
- [x] Verification: `npx tsc --noEmit` clean; `npm run build` clean
(app.bundle.css 366.6kb, app.bundle.js 2.7mb).
### Follow-ups (out of scope for initial PR)
- [ ] **Global concurrency cap / minimum interval.** Each
`HTTPValueStream` runs its own task at `interval_s` (min 1s); no
project-wide cap. Reviewer flagged: pick a min (e.g. 5s) + max
active runtimes (e.g. 32) + shared `httpx.AsyncClient` with
`limits=httpx.Limits(max_connections=N)`.
- [ ] **DNS-rebinding hardening.** `safe_request_bounded` validates
the URL hostname's resolved IPs once; httpx independently
re-resolves. The window is short but not zero. True fix: pin
to the validated IP + set Host header (and SNI for HTTPS). This
affects every outbound caller (`safe_fetch`, weather, image
sources) — handle as a project-wide hardening, not local to
this feature.
- [ ] **`delete_http_endpoint` orphan refs.** When an admin deletes an
endpoint referenced by N value sources, the value-stream task
keeps polling until its source is also deleted. Same shape as
the MQTT defect — fix both together (refuse-with-409 when in
use, or cascade value-source deletion).
- [ ] **Per-endpoint `connected` / last-poll status on the response**
(frontend agent flagged). `HTTPEndpointResponse` has no live
status, unlike HA/MQTT sources. Card LEDs default to "on".
Could aggregate `last_status_code` / `last_error` from all
`HTTPValueStream` instances referencing the endpoint and surface
on `GET /http/endpoints/{id}`.
- [x] **Per-endpoint live `/test` after save** — added `POST
/http/endpoints/{id}/test` (runs stored config server-side so the
auth token never round-trips) and wired a flask-icon test action
on the endpoint card (toasts the result). Custom-headers section
and inline test-result UI in the editor modal also restyled to
match the `.group-child-row` and result-card vocabulary.
- [ ] **Dedicated icon for HTTP value source / endpoint** (frontend
agent flagged). Both use `P.globe` — visually fine in practice
but adding a `cable`/`webhook` glyph in `icon-paths.ts` would
improve differentiation.
## Multi-broker MQTT refactor ## Multi-broker MQTT refactor
Goal: drop the global `MQTTService` / `MQTTConfig`. Every MQTT consumer Goal: drop the global `MQTTService` / `MQTTConfig`. Every MQTT consumer
+91 -7
View File
@@ -1352,7 +1352,7 @@
"color_strip.test_device.hint": "Select a device to send test pixels to when clicking edge toggles", "color_strip.test_device.hint": "Select a device to send test pixels to when clicking edge toggles",
"color_strip.leds": "LED count", "color_strip.leds": "LED count",
"color_strip.led_count": "LED Count:", "color_strip.led_count": "LED Count:",
"color_strip.led_count.hint": "Total number of LEDs on the physical strip. For screen sources: 0 = auto from calibration (extra LEDs not mapped to edges will be black). For static color: set to match your device LED count.", "color_strip.led_count.hint": "Total number of LEDs on the physical strip. For screen sources: 0 = auto from calibration (extra LEDs not mapped to edges will be black). For single color: set to match your device LED count.",
"color_strip.created": "Color strip source created", "color_strip.created": "Color strip source created",
"color_strip.updated": "Color strip source updated", "color_strip.updated": "Color strip source updated",
"color_strip.deleted": "Color strip source deleted", "color_strip.deleted": "Color strip source deleted",
@@ -1360,17 +1360,17 @@
"color_strip.delete.referenced": "Cannot delete: this source is in use by a target", "color_strip.delete.referenced": "Cannot delete: this source is in use by a target",
"color_strip.error.name_required": "Please enter a name", "color_strip.error.name_required": "Please enter a name",
"color_strip.type": "Type:", "color_strip.type": "Type:",
"color_strip.type.hint": "Picture Source derives LED colors from a screen capture. Static Color fills all LEDs with a single constant color. Gradient distributes a color gradient across all LEDs. Color Cycle smoothly cycles through a user-defined list of colors. Composite stacks multiple sources as blended layers. Audio Reactive drives LEDs from real-time audio input. API Input receives raw LED colors from external clients via REST or WebSocket.", "color_strip.type.hint": "Picture Source derives LED colors from a screen capture. Single Color fills all LEDs with a single constant color. Gradient distributes a color gradient across all LEDs. Color Cycle smoothly cycles through a user-defined list of colors. Composite stacks multiple sources as blended layers. Audio Reactive drives LEDs from real-time audio input. API Input receives raw LED colors from external clients via REST or WebSocket.",
"color_strip.type.picture": "Picture Source", "color_strip.type.picture": "Picture Source",
"color_strip.type.picture.desc": "Colors from screen capture", "color_strip.type.picture.desc": "Colors from screen capture",
"color_strip.type.picture_advanced": "Multi-Monitor", "color_strip.type.picture_advanced": "Multi-Monitor",
"color_strip.type.picture_advanced.desc": "Line-based calibration across monitors", "color_strip.type.picture_advanced.desc": "Line-based calibration across monitors",
"color_strip.type.static": "Static Color", "color_strip.type.single_color": "Single Color",
"color_strip.type.static.desc": "Single solid color fill", "color_strip.type.single_color.desc": "Single solid color fill",
"color_strip.type.gradient": "Gradient", "color_strip.type.gradient": "Gradient",
"color_strip.type.gradient.desc": "Smooth color transition across LEDs", "color_strip.type.gradient.desc": "Smooth color transition across LEDs",
"color_strip.static_color": "Color:", "color_strip.single_color": "Color:",
"color_strip.static_color.hint": "The solid color that will be sent to all LEDs on the strip.", "color_strip.single_color.hint": "The solid color that will be sent to all LEDs on the strip.",
"color_strip.gradient.preview": "Gradient:", "color_strip.gradient.preview": "Gradient:",
"color_strip.gradient.preview.hint": "Visual preview. Click the marker track below to add a stop. Drag markers to reposition.", "color_strip.gradient.preview.hint": "Visual preview. Click the marker track below to add a stop. Drag markers to reposition.",
"color_strip.gradient.easing": "Easing:", "color_strip.gradient.easing": "Easing:",
@@ -2961,5 +2961,89 @@
"pairing.success": "Paired successfully", "pairing.success": "Paired successfully",
"pairing.not_ready": "Device didn't respond. Press the pairing button on your device, then try again.", "pairing.not_ready": "Device didn't respond. Press the pairing button on your device, then try again.",
"pairing.failed": "Pairing failed: {detail}", "pairing.failed": "Pairing failed: {detail}",
"pairing.failed_prefix": "Pairing failed:" "pairing.failed_prefix": "Pairing failed:",
"streams.group.http": "HTTP",
"http_endpoint.group.title": "HTTP Endpoints",
"http_endpoint.add": "Add HTTP Endpoint",
"http_endpoint.edit": "Edit HTTP Endpoint",
"http_endpoint.section.request": "Request",
"http_endpoint.section.headers": "Headers",
"http_endpoint.name": "Name:",
"http_endpoint.name.placeholder": "Plex now-playing",
"http_endpoint.name.hint": "A descriptive name for this endpoint",
"http_endpoint.url": "URL:",
"http_endpoint.url.hint": "Full http(s) URL to poll. Local addresses are allowed.",
"http_endpoint.method": "Method:",
"http_endpoint.method.get.desc": "Fetch the response body.",
"http_endpoint.method.head.desc": "Status code only — no body. Great for liveness probes.",
"http_endpoint.auth_token": "Auth Token (optional):",
"http_endpoint.auth_token.hint": "Sent as 'Authorization: Bearer <token>'. Add a custom Authorization header to override.",
"http_endpoint.auth_token.edit_hint": "Leave blank to keep the current token",
"http_endpoint.auth_token.reveal": "Show / hide token",
"http_endpoint.auth.set": "Auth",
"http_endpoint.timeout": "Timeout (s):",
"http_endpoint.timeout.hint": "Maximum seconds to wait for a single request.",
"http_endpoint.test": "Test request",
"http_endpoint.test.pending": "Testing…",
"http_endpoint.test.success": "OK",
"http_endpoint.test.failed": "Failed",
"http_endpoint.test.body.json": "JSON body",
"http_endpoint.test.body.text": "Response body",
"http_endpoint.headers": "Custom Headers:",
"http_endpoint.headers.hint": "Optional request headers (e.g. X-API-Key, Accept).",
"http_endpoint.headers.add": "Add header",
"http_endpoint.headers.empty": "No custom headers — defaults will be sent.",
"http_endpoint.headers.name_placeholder": "Header name",
"http_endpoint.headers.value_placeholder": "Header value",
"http_endpoint.headers.count": "{n} headers",
"http_endpoint.description": "Description (optional):",
"http_endpoint.created": "HTTP endpoint created",
"http_endpoint.updated": "HTTP endpoint updated",
"http_endpoint.deleted": "HTTP endpoint deleted",
"http_endpoint.delete.confirm": "Delete this HTTP endpoint? Value sources that reference it will need to be repointed.",
"http_endpoint.error.name_required": "Name is required",
"http_endpoint.error.url_required": "URL is required",
"http_endpoint.error.timeout_invalid": "Timeout must be a positive number",
"http_endpoint.error.load": "Failed to load HTTP endpoint",
"section.empty.http_endpoints": "No HTTP endpoints yet. Click + to add one.",
"device.icon.entity.http_endpoint": "HTTP endpoint",
"value_source.type.http": "HTTP Poll",
"value_source.type.http.desc": "Polls an HTTP endpoint at a fixed cadence and maps the extracted value to 0-1.",
"value_source.http.endpoint": "HTTP Endpoint:",
"value_source.http.endpoint.hint": "Pick a saved endpoint from the HTTP integrations tab.",
"value_source.http.json_path": "JSON Path:",
"value_source.http.json_path.hint": "Empty = use raw response body. Use dotted/indexed path, e.g. MediaContainer.Metadata[0].title",
"value_source.http.interval": "Interval (s):",
"value_source.http.interval.hint": "Polling cadence in seconds. Multiple value sources can share one endpoint at different intervals.",
"value_source.http.min_value": "Min Value:",
"value_source.http.min_value.hint": "Raw extracted value that maps to output 0.0 (for normalisation).",
"value_source.http.max_value": "Max Value:",
"value_source.http.max_value.hint": "Raw extracted value that maps to output 1.0 (for normalisation).",
"value_source.http.modulator.summary": "Modulator mapping (optional)",
"value_source.http.modulator.hint": "Only used when this source drives brightness or color. Automation rules read the raw extracted value and ignore these settings.",
"value_source.http.endpoint_required": "HTTP endpoint is required",
"value_source.http.interval_invalid": "Interval must be at least 1 second",
"automations.rule.http_poll": "HTTP Poll",
"automations.rule.http_poll.desc": "Activate when the latest extracted value from an HTTP value source matches.",
"automations.rule.http_poll.hint": "Compares the latest extracted value against your input. The value source decides what gets extracted (raw body or JSON path).",
"automations.rule.http_poll.value_source": "HTTP Value Source",
"automations.rule.http_poll.operator": "Operator",
"automations.rule.http_poll.value": "Value",
"automations.rule.http_poll.value.placeholder": "playing",
"automations.rule.http_poll.no_source": "(no source)",
"automations.rule.http_poll.raw_body": "Raw body",
"automations.rule.http_poll.operator.equals": "Equals",
"automations.rule.http_poll.operator.equals.desc": "Exact string match.",
"automations.rule.http_poll.operator.not_equals": "Not equals",
"automations.rule.http_poll.operator.not_equals.desc": "Activates when the value is anything other than this.",
"automations.rule.http_poll.operator.contains": "Contains",
"automations.rule.http_poll.operator.contains.desc": "Substring match.",
"automations.rule.http_poll.operator.regex": "Regex",
"automations.rule.http_poll.operator.regex.desc": "JavaScript-style regular expression.",
"automations.rule.http_poll.operator.gt": "Greater than",
"automations.rule.http_poll.operator.gt.desc": "Numeric comparison (>) — requires numeric output.",
"automations.rule.http_poll.operator.lt": "Less than",
"automations.rule.http_poll.operator.lt.desc": "Numeric comparison (<) — requires numeric output.",
"automations.rule.http_poll.operator.exists": "Exists",
"automations.rule.http_poll.operator.exists.desc": "Activates whenever a value is successfully extracted (ignores the value)."
} }
+90 -6
View File
@@ -1393,17 +1393,17 @@
"color_strip.delete.referenced": "Невозможно удалить: источник используется в цели", "color_strip.delete.referenced": "Невозможно удалить: источник используется в цели",
"color_strip.error.name_required": "Введите название", "color_strip.error.name_required": "Введите название",
"color_strip.type": "Тип:", "color_strip.type": "Тип:",
"color_strip.type.hint": "Источник изображения получает цвета светодиодов из захвата экрана. Статический цвет заполняет все светодиоды одним постоянным цветом. Градиент распределяет цветовой градиент по всем светодиодам. Смена цвета плавно циклически переключается между заданными цветами. Композит накладывает несколько источников как смешанные слои. Аудиореактив управляет LED от аудиосигнала в реальном времени. API-ввод принимает массивы цветов LED от внешних клиентов через REST или WebSocket.", "color_strip.type.hint": "Источник изображения получает цвета светодиодов из захвата экрана. Один цвет заполняет все светодиоды одним постоянным цветом. Градиент распределяет цветовой градиент по всем светодиодам. Смена цвета плавно циклически переключается между заданными цветами. Композит накладывает несколько источников как смешанные слои. Аудиореактив управляет LED от аудиосигнала в реальном времени. API-ввод принимает массивы цветов LED от внешних клиентов через REST или WebSocket.",
"color_strip.type.picture": "Источник изображения", "color_strip.type.picture": "Источник изображения",
"color_strip.type.picture.desc": "Цвета из захвата экрана", "color_strip.type.picture.desc": "Цвета из захвата экрана",
"color_strip.type.picture_advanced": "Мультимонитор", "color_strip.type.picture_advanced": "Мультимонитор",
"color_strip.type.picture_advanced.desc": "Калибровка линиями по нескольким мониторам", "color_strip.type.picture_advanced.desc": "Калибровка линиями по нескольким мониторам",
"color_strip.type.static": "Статический цвет", "color_strip.type.single_color": "Один цвет",
"color_strip.type.static.desc": "Заливка одним цветом", "color_strip.type.single_color.desc": "Заливка одним цветом",
"color_strip.type.gradient": "Градиент", "color_strip.type.gradient": "Градиент",
"color_strip.type.gradient.desc": "Плавный переход цветов по ленте", "color_strip.type.gradient.desc": "Плавный переход цветов по ленте",
"color_strip.static_color": "Цвет:", "color_strip.single_color": "Цвет:",
"color_strip.static_color.hint": "Статический цвет, который будет отправлен на все светодиоды полосы.", "color_strip.single_color.hint": "Один сплошной цвет, который будет отправлен на все светодиоды полосы.",
"color_strip.gradient.preview": "Градиент:", "color_strip.gradient.preview": "Градиент:",
"color_strip.gradient.preview.hint": "Предпросмотр градиента. Нажмите на дорожку маркеров чтобы добавить остановку. Перетащите маркеры для изменения позиции.", "color_strip.gradient.preview.hint": "Предпросмотр градиента. Нажмите на дорожку маркеров чтобы добавить остановку. Перетащите маркеры для изменения позиции.",
"color_strip.gradient.stops": "Цветовые остановки:", "color_strip.gradient.stops": "Цветовые остановки:",
@@ -2641,5 +2641,89 @@
"pairing.success": "Успешно сопряжено", "pairing.success": "Успешно сопряжено",
"pairing.not_ready": "Устройство не ответило. Нажмите кнопку сопряжения на устройстве и попробуйте снова.", "pairing.not_ready": "Устройство не ответило. Нажмите кнопку сопряжения на устройстве и попробуйте снова.",
"pairing.failed": "Сопряжение не удалось: {detail}", "pairing.failed": "Сопряжение не удалось: {detail}",
"pairing.failed_prefix": "Сопряжение не удалось:" "pairing.failed_prefix": "Сопряжение не удалось:",
"streams.group.http": "HTTP",
"http_endpoint.group.title": "HTTP-эндпоинты",
"http_endpoint.add": "Добавить HTTP-эндпоинт",
"http_endpoint.edit": "Изменить HTTP-эндпоинт",
"http_endpoint.section.request": "Запрос",
"http_endpoint.section.headers": "Заголовки",
"http_endpoint.name": "Имя:",
"http_endpoint.name.placeholder": "Plex now-playing",
"http_endpoint.name.hint": "Описательное имя для этого эндпоинта",
"http_endpoint.url": "URL:",
"http_endpoint.url.hint": "Полный http(s)-URL для опроса. Локальные адреса разрешены.",
"http_endpoint.method": "Метод:",
"http_endpoint.method.get.desc": "Получить тело ответа.",
"http_endpoint.method.head.desc": "Только код статуса — без тела. Подходит для проверки доступности.",
"http_endpoint.auth_token": "Токен авторизации (необязательно):",
"http_endpoint.auth_token.hint": "Отправляется как 'Authorization: Bearer <token>'. Добавьте свой Authorization-заголовок, чтобы переопределить.",
"http_endpoint.auth_token.edit_hint": "Оставьте пустым, чтобы сохранить текущий токен",
"http_endpoint.auth_token.reveal": "Показать / скрыть токен",
"http_endpoint.auth.set": "Авторизация",
"http_endpoint.timeout": "Таймаут (с):",
"http_endpoint.timeout.hint": "Максимальное время ожидания одного запроса (в секундах).",
"http_endpoint.test": "Тестовый запрос",
"http_endpoint.test.pending": "Проверка…",
"http_endpoint.test.success": "Успех",
"http_endpoint.test.failed": "Ошибка",
"http_endpoint.test.body.json": "JSON-тело",
"http_endpoint.test.body.text": "Тело ответа",
"http_endpoint.headers": "Заголовки:",
"http_endpoint.headers.hint": "Дополнительные заголовки запроса (например, X-API-Key, Accept).",
"http_endpoint.headers.add": "Добавить заголовок",
"http_endpoint.headers.empty": "Нет дополнительных заголовков — будут отправлены значения по умолчанию.",
"http_endpoint.headers.name_placeholder": "Имя заголовка",
"http_endpoint.headers.value_placeholder": "Значение",
"http_endpoint.headers.count": "{n} заголовков",
"http_endpoint.description": "Описание (необязательно):",
"http_endpoint.created": "HTTP-эндпоинт создан",
"http_endpoint.updated": "HTTP-эндпоинт обновлён",
"http_endpoint.deleted": "HTTP-эндпоинт удалён",
"http_endpoint.delete.confirm": "Удалить этот HTTP-эндпоинт? Источники-значений, ссылающиеся на него, потребуется перенастроить.",
"http_endpoint.error.name_required": "Имя обязательно",
"http_endpoint.error.url_required": "URL обязателен",
"http_endpoint.error.timeout_invalid": "Таймаут должен быть положительным числом",
"http_endpoint.error.load": "Не удалось загрузить HTTP-эндпоинт",
"section.empty.http_endpoints": "Пока нет HTTP-эндпоинтов. Нажмите +, чтобы добавить.",
"device.icon.entity.http_endpoint": "HTTP-эндпоинт",
"value_source.type.http": "HTTP-опрос",
"value_source.type.http.desc": "Периодически опрашивает HTTP-эндпоинт и сопоставляет извлечённое значение с диапазоном 0–1.",
"value_source.http.endpoint": "HTTP-эндпоинт:",
"value_source.http.endpoint.hint": "Выберите сохранённый эндпоинт во вкладке HTTP-интеграций.",
"value_source.http.json_path": "JSON-путь:",
"value_source.http.json_path.hint": "Пусто = использовать необработанное тело ответа. Используйте путь с точками и индексами, например MediaContainer.Metadata[0].title",
"value_source.http.interval": "Интервал (с):",
"value_source.http.interval.hint": "Период опроса в секундах. Несколько источников могут использовать один эндпоинт с разными интервалами.",
"value_source.http.min_value": "Мин. значение:",
"value_source.http.min_value.hint": "Извлечённое значение, которое отображается в выход 0.0 (для нормализации).",
"value_source.http.max_value": "Макс. значение:",
"value_source.http.max_value.hint": "Извлечённое значение, которое отображается в выход 1.0 (для нормализации).",
"value_source.http.modulator.summary": "Сопоставление для модулятора (необязательно)",
"value_source.http.modulator.hint": "Используется только когда этот источник управляет яркостью или цветом. Правила автоматизации читают извлечённое значение в исходном виде и игнорируют эти настройки.",
"value_source.http.endpoint_required": "Требуется HTTP-эндпоинт",
"value_source.http.interval_invalid": "Интервал должен быть не меньше 1 секунды",
"automations.rule.http_poll": "HTTP-опрос",
"automations.rule.http_poll.desc": "Срабатывает, когда последнее значение HTTP-источника соответствует условию.",
"automations.rule.http_poll.hint": "Сравнивает последнее извлечённое значение с вашим вводом. Что именно извлекается (тело или JSON-путь), задаётся в источнике-значении.",
"automations.rule.http_poll.value_source": "HTTP источник-значения",
"automations.rule.http_poll.operator": "Оператор",
"automations.rule.http_poll.value": "Значение",
"automations.rule.http_poll.value.placeholder": "playing",
"automations.rule.http_poll.no_source": "(нет источника)",
"automations.rule.http_poll.raw_body": "Тело ответа",
"automations.rule.http_poll.operator.equals": "Равно",
"automations.rule.http_poll.operator.equals.desc": "Точное совпадение строки.",
"automations.rule.http_poll.operator.not_equals": "Не равно",
"automations.rule.http_poll.operator.not_equals.desc": "Срабатывает, когда значение отличается.",
"automations.rule.http_poll.operator.contains": "Содержит",
"automations.rule.http_poll.operator.contains.desc": "Поиск подстроки.",
"automations.rule.http_poll.operator.regex": "Regex",
"automations.rule.http_poll.operator.regex.desc": "Регулярное выражение в стиле JavaScript.",
"automations.rule.http_poll.operator.gt": "Больше",
"automations.rule.http_poll.operator.gt.desc": "Числовое сравнение (>) — нужно числовое значение.",
"automations.rule.http_poll.operator.lt": "Меньше",
"automations.rule.http_poll.operator.lt.desc": "Числовое сравнение (<) — нужно числовое значение.",
"automations.rule.http_poll.operator.exists": "Существует",
"automations.rule.http_poll.operator.exists.desc": "Срабатывает, когда значение успешно извлечено (само значение игнорируется)."
} }
+90 -6
View File
@@ -1390,17 +1390,17 @@
"color_strip.delete.referenced": "无法删除:此源正在被目标使用", "color_strip.delete.referenced": "无法删除:此源正在被目标使用",
"color_strip.error.name_required": "请输入名称", "color_strip.error.name_required": "请输入名称",
"color_strip.type": "类型:", "color_strip.type": "类型:",
"color_strip.type.hint": "图片源从屏幕采集推导 LED 颜色。静态颜色用单一颜色填充所有 LED。渐变在所有 LED 上分布颜色渐变。颜色循环平滑循环用户定义的颜色列表。组合将多个源作为混合图层叠加。音频响应从实时音频输入驱动 LED。API 输入通过 REST 或 WebSocket 从外部客户端接收原始 LED 颜色。", "color_strip.type.hint": "图片源从屏幕采集推导 LED 颜色。色用单一颜色填充所有 LED。渐变在所有 LED 上分布颜色渐变。颜色循环平滑循环用户定义的颜色列表。组合将多个源作为混合图层叠加。音频响应从实时音频输入驱动 LED。API 输入通过 REST 或 WebSocket 从外部客户端接收原始 LED 颜色。",
"color_strip.type.picture": "图片源", "color_strip.type.picture": "图片源",
"color_strip.type.picture.desc": "从屏幕捕获获取颜色", "color_strip.type.picture.desc": "从屏幕捕获获取颜色",
"color_strip.type.picture_advanced": "多显示器", "color_strip.type.picture_advanced": "多显示器",
"color_strip.type.picture_advanced.desc": "跨显示器的线条校准", "color_strip.type.picture_advanced.desc": "跨显示器的线条校准",
"color_strip.type.static": "静态颜色", "color_strip.type.single_color": "色",
"color_strip.type.static.desc": "单色填充", "color_strip.type.single_color.desc": "单色填充",
"color_strip.type.gradient": "渐变", "color_strip.type.gradient": "渐变",
"color_strip.type.gradient.desc": "LED上的平滑颜色过渡", "color_strip.type.gradient.desc": "LED上的平滑颜色过渡",
"color_strip.static_color": "颜色:", "color_strip.single_color": "颜色:",
"color_strip.static_color.hint": "将发送到灯带上所有 LED 的纯色。", "color_strip.single_color.hint": "将发送到灯带上所有 LED 的纯色。",
"color_strip.gradient.preview": "渐变:", "color_strip.gradient.preview": "渐变:",
"color_strip.gradient.preview.hint": "可视预览。点击下方标记轨道添加色标。拖动标记重新定位。", "color_strip.gradient.preview.hint": "可视预览。点击下方标记轨道添加色标。拖动标记重新定位。",
"color_strip.gradient.stops": "色标:", "color_strip.gradient.stops": "色标:",
@@ -2636,5 +2636,89 @@
"pairing.success": "配对成功", "pairing.success": "配对成功",
"pairing.not_ready": "设备未响应。请按下设备上的配对按钮后重试。", "pairing.not_ready": "设备未响应。请按下设备上的配对按钮后重试。",
"pairing.failed": "配对失败:{detail}", "pairing.failed": "配对失败:{detail}",
"pairing.failed_prefix": "配对失败:" "pairing.failed_prefix": "配对失败:",
"streams.group.http": "HTTP",
"http_endpoint.group.title": "HTTP 端点",
"http_endpoint.add": "添加 HTTP 端点",
"http_endpoint.edit": "编辑 HTTP 端点",
"http_endpoint.section.request": "请求",
"http_endpoint.section.headers": "请求头",
"http_endpoint.name": "名称:",
"http_endpoint.name.placeholder": "Plex 正在播放",
"http_endpoint.name.hint": "此端点的描述性名称",
"http_endpoint.url": "URL",
"http_endpoint.url.hint": "要轮询的完整 http(s) URL,允许使用本地地址。",
"http_endpoint.method": "方法:",
"http_endpoint.method.get.desc": "获取响应体。",
"http_endpoint.method.head.desc": "仅返回状态码,不返回响应体。适合健康检查。",
"http_endpoint.auth_token": "认证令牌(可选):",
"http_endpoint.auth_token.hint": "作为 'Authorization: Bearer <token>' 发送。在请求头中添加自定义 Authorization 可覆盖。",
"http_endpoint.auth_token.edit_hint": "留空以保留当前令牌",
"http_endpoint.auth_token.reveal": "显示 / 隐藏令牌",
"http_endpoint.auth.set": "认证",
"http_endpoint.timeout": "超时(秒):",
"http_endpoint.timeout.hint": "单次请求的最长等待秒数。",
"http_endpoint.test": "测试请求",
"http_endpoint.test.pending": "测试中…",
"http_endpoint.test.success": "成功",
"http_endpoint.test.failed": "失败",
"http_endpoint.test.body.json": "JSON 主体",
"http_endpoint.test.body.text": "响应主体",
"http_endpoint.headers": "自定义请求头:",
"http_endpoint.headers.hint": "可选的请求头(如 X-API-Key、Accept)。",
"http_endpoint.headers.add": "添加请求头",
"http_endpoint.headers.empty": "没有自定义请求头 — 将发送默认值。",
"http_endpoint.headers.name_placeholder": "请求头名称",
"http_endpoint.headers.value_placeholder": "值",
"http_endpoint.headers.count": "{n} 个请求头",
"http_endpoint.description": "描述(可选):",
"http_endpoint.created": "已创建 HTTP 端点",
"http_endpoint.updated": "已更新 HTTP 端点",
"http_endpoint.deleted": "已删除 HTTP 端点",
"http_endpoint.delete.confirm": "删除此 HTTP 端点?引用它的值源需要重新指向其他端点。",
"http_endpoint.error.name_required": "需要名称",
"http_endpoint.error.url_required": "需要 URL",
"http_endpoint.error.timeout_invalid": "超时必须为正数",
"http_endpoint.error.load": "加载 HTTP 端点失败",
"section.empty.http_endpoints": "暂无 HTTP 端点。点击 + 添加一个。",
"device.icon.entity.http_endpoint": "HTTP 端点",
"value_source.type.http": "HTTP 轮询",
"value_source.type.http.desc": "按固定间隔轮询 HTTP 端点,并将提取的值映射到 0–1。",
"value_source.http.endpoint": "HTTP 端点:",
"value_source.http.endpoint.hint": "从 HTTP 集成选项卡中选择已保存的端点。",
"value_source.http.json_path": "JSON 路径:",
"value_source.http.json_path.hint": "为空则使用原始响应体。使用点号/索引路径,例如 MediaContainer.Metadata[0].title",
"value_source.http.interval": "间隔(秒):",
"value_source.http.interval.hint": "轮询间隔(秒)。多个值源可以以不同间隔共享同一端点。",
"value_source.http.min_value": "最小值:",
"value_source.http.min_value.hint": "映射到输出 0.0 的原始值(用于归一化)。",
"value_source.http.max_value": "最大值:",
"value_source.http.max_value.hint": "映射到输出 1.0 的原始值(用于归一化)。",
"value_source.http.modulator.summary": "调制映射(可选)",
"value_source.http.modulator.hint": "仅当此源用于驱动亮度或颜色时使用。自动化规则会直接读取提取的原始值,并忽略这些设置。",
"value_source.http.endpoint_required": "需要 HTTP 端点",
"value_source.http.interval_invalid": "间隔至少为 1 秒",
"automations.rule.http_poll": "HTTP 轮询",
"automations.rule.http_poll.desc": "当 HTTP 值源的最新提取值匹配时激活。",
"automations.rule.http_poll.hint": "将最新的提取值与您的输入进行比较。提取的内容(原始响应体或 JSON 路径)由值源决定。",
"automations.rule.http_poll.value_source": "HTTP 值源",
"automations.rule.http_poll.operator": "运算符",
"automations.rule.http_poll.value": "值",
"automations.rule.http_poll.value.placeholder": "playing",
"automations.rule.http_poll.no_source": "(无来源)",
"automations.rule.http_poll.raw_body": "原始响应体",
"automations.rule.http_poll.operator.equals": "等于",
"automations.rule.http_poll.operator.equals.desc": "精确字符串匹配。",
"automations.rule.http_poll.operator.not_equals": "不等于",
"automations.rule.http_poll.operator.not_equals.desc": "当值不同时激活。",
"automations.rule.http_poll.operator.contains": "包含",
"automations.rule.http_poll.operator.contains.desc": "子字符串匹配。",
"automations.rule.http_poll.operator.regex": "正则",
"automations.rule.http_poll.operator.regex.desc": "JavaScript 风格的正则表达式。",
"automations.rule.http_poll.operator.gt": "大于",
"automations.rule.http_poll.operator.gt.desc": "数值比较 (>) — 需要数值输出。",
"automations.rule.http_poll.operator.lt": "小于",
"automations.rule.http_poll.operator.lt.desc": "数值比较 (<) — 需要数值输出。",
"automations.rule.http_poll.operator.exists": "存在",
"automations.rule.http_poll.operator.exists.desc": "只要成功提取出值就激活(忽略值本身)。"
} }