Сторонние сервисы в веб-интерфейсе Wiren Board

From Wiren Board
This is the approved revision of this page, as well as being the most recent.

Это черновик страницы. Последняя правка сделана 10.08.2026 пользователем D.Nikolaev.


Описание

Доступно в релизе testing

На контроллер часто устанавливают сторонние сервисы с собственным веб-интерфейсом: Node-RED, Grafana, zigbee2mqtt и другие. После установки можно столкнуться со следующими проблемами:

  • Отсутствие защиты — сторонний софт работает на отдельном порту и по умолчанию открыт для любого, у кого есть доступ к контроллеру по сети.
  • Сложная навигация — если у вас несколько контроллеров, трудно держать в голове, какие именно сервисы установлены на каждом контроллере и на каких портах работает каждый сервис. Но даже если вы всё помните, адрес приложения каждый раз приходится вводить в строку браузера вручную.

Веб-интерфейс контроллера решает обе проблемы. Вы можете использовать эти решения по отдельности или вместе:

  • Авторизация (Безопасность) — закрывает доступ к приложению паролем от контроллера. Войти смогут только пользователи с нужным уровнем прав.
  • Ссылка в меню (Удобство) — добавляет ссылку на приложение в боковое меню веб-интерфейса. Сервис откроется в новой вкладке браузера — вам больше не придётся вручную вводить его IP-адрес и порт.

Что выбрать

«Гейт» (от английского gate — ворота, шлюз) — это правило встроенного веб-сервера контроллера, которое стоит на входе к сервису и пропускает к нему только пользователей, вошедших в веб-интерфейс. Настройка описывается двумя видами JSON-файлов, у каждого своя задача:

  • Файл гейта (gates.d) отвечает за сеть и авторизацию: на каком порту принимать запросы, кого пускать, работать ли по HTTPS.
  • Файл меню (custom-menu) отвечает только за навигацию: добавляет ссылку в боковое меню, ничего не защищает.

Если в файле гейта указать раздел menu, гейт сам добавит пункт в меню — под капотом он создаёт запись в том же формате, что и файл меню. Отдельно описывать пункт не нужно. Обратное неверно: файл меню сам по себе гейт не создаёт.

Отсюда три сценария:

Что нужно Что сделать
Просто ссылка в меню, без авторизации (сервис защищён сам или работает на другом устройстве) Только файл меню
Закрыть сервис авторизацией и сразу показать пункт в меню Файл гейта с разделом menu
Закрыть сервис авторизацией, без пункта в меню Файл гейта без раздела menu — при желании пункт можно добавить отдельным файлом меню со ссылкой /open-<имя>
Гейт добавляет пункт меню сам; файл меню создаёт только ссылку и гейт не поднимает

Сервис за авторизацией контроллера

В веб-интерфейсе должен быть создан хотя бы один пользователь — иначе защиты не будет. Пока пользователей нет, контроллер открывается с правами администратора без запроса входа, и гейт пропустит любого. Создайте пользователей в разделе Аутентификация и авторизация пользователя.

Сам сервис слушает только внутренний адрес 127.0.0.1 и снаружи недоступен, а гейт принимает соединения на отдельном внешнем порту и проверяет каждый запрос:

  • пользователя без активного входа браузер перенаправит на форму входа веб-интерфейса, а после входа вернёт обратно к сервису;
  • запрос от программы или скрипта без входа получит ответ 401;
  • если в настройках контроллера включён HTTPS — гейт тоже работает по HTTPS, отдельно настраивать ничего не нужно.
Запрос проходит через гейт к сервису на 127.0.0.1; без входа пользователь уходит на форму входа и после неё возвращается к сервису

Настройка

Команды выполняются на контроллере — например, по SSH под пользователем root.

  1. Настройте сервис так, чтобы он слушал только адрес 127.0.0.1 — иначе внутренний порт останется доступен снаружи в обход авторизации. Как это сделать смотрите в документации на соответствующий сервис.
  2. Откройте новый файл гейта в редакторе. Имя файла — строчные латинские буквы, цифры и дефис, расширение .json; в примере это my-service.json:
    nano /etc/wb-homeui/gates.d/my-service.json
    
  3. Вставьте описание гейта и сохраните файл (в nano — Ctrl+O, Enter, затем Ctrl+X):
    {
      "internalPort": 9000,
      "externalPort": 29000,
      "role": "admin",
      "menu": { "title": { "ru": "Мой сервис", "en": "My service" } }
    }
    
  4. Проверьте и примените описание:
    wb-homeui-gates check
    wb-homeui-gates apply
    
    Команда check показывает, какие гейты получатся из ваших файлов, а для файлов с ошибками — причину. Команда apply создаёт настройки веб-сервера и перезапускает его. Если новая конфигурация оказалась неработоспособной, apply печатает причину и возвращает веб-сервер к прежнему состоянию — сломать веб-интерфейс или соседние гейты не получится. Поправьте файл и запустите apply ещё раз.

Поля файла описания:

Поле Обязательное Что задаёт
externalPort да Внешний порт гейта, от 1024 до 65535. Не должен совпадать с портами других гейтов и их сервисов.
internalPort да Порт сервиса на 127.0.0.1, тоже от 1024 до 65535.
role нет, по умолчанию admin Минимальный уровень доступа: user, operator или admin. Пускает пользователей с этим уровнем и выше; user пускает любого вошедшего.
auth нет, по умолчанию true false — пускать на сервис без авторизации.
menu нет Пункт в разделе меню «Интеграции». menu.title — подписи на русском и английском, достаточно одной. Пользователям с уровнем ниже role пункт не показывается.

Каталог /etc/wb-homeui сохраняется при обновлении прошивки контроллера. Перенос в защищённый от обновлений раздел завершается после перезагрузки — после первой настройки перезагрузите контроллер.

Как открыть сервис

  • Через пункт меню — если в описании гейта задано поле menu.
  • По адресу http://<адрес контроллера>/open-<имя>, где имя — имя JSON-файла без расширения: контроллер перенаправит вас на порт гейта.

Открывайте сервис по тому же адресу — IP или доменному имени, — по которому открываете веб-интерфейс. Если зайти на порт гейта по другому адресу, вход не сработает: браузер будет бесконечно возвращать вас на форму входа.

Свои настройки веб-сервера

Если гейту нужны нестандартные настройки nginx — увеличенные таймауты, дополнительные location — создайте рядом с JSON-файлом файл <имя>.nginx.inc: его содержимое попадёт внутрь server-блока гейта. Ошибку в таком файле поймает проверка при apply: применение откатится, ваш файл останется на месте — поправьте его и повторите команду.

Ссылка на сервис в меню

Этот способ добавляет в боковое меню ссылку — на сервис за гейтом (адрес /open-<имя>), на сервис со своей авторизацией или на сервис на другом устройстве. Сам по себе он ничего не защищает. Файлы создаются в каталоге /etc/wb-homeui/custom-menu/, команды выполняются на контроллере под root.

Куда попадёт пункт, зависит от его поля id:

  • новый id — пункт добавляется отдельной строкой в конец бокового меню;
  • id совпадает с существующим разделом (например, integrations — раздел «Интеграции») — пункт вливается в этот раздел;
  • пункт с children становится раскрывающимся разделом со вложенными ссылками.

Это ваш контроллер — класть пункт можно куда угодно. Откройте новый файл (имя любое) в редакторе:

nano /etc/wb-homeui/custom-menu/my-service.json

Вставьте один из вариантов ниже и сохраните файл (в nano — Ctrl+O, Enter, затем Ctrl+X). Изменения видны после обновления страницы веб-интерфейса.

Адрес в url лучше делать относительным: /open-<имя> для сервиса за гейтом или путь от корня (/…) — такая ссылка следует за адресом контроллера и не сломается при смене IP или доменного имени. Полный адрес http://<IP>:<порт> нужен только для сервиса на другом устройстве; при смене того адреса ссылку придётся править вручную.

Пункт «Мой сервис» в разделе «Интеграции»

Вариант 1. В раздел «Интеграции» (рекомендуется). Ссылка на сервис, закрытый гейтом на этом контроллере: адрес /open-<имя> не привязан к IP. Обёртка с id: "integrations" и списком children кладёт пункт в тот же раздел, что и гейт.

[
  {
    "id": "integrations",
    "children": [
      {
        "id": "my-service",
        "title": { "ru": "Мой сервис", "en": "My service" },
        "url": "/open-my-service",
        "isExternal": true,
        "openInNewTab": true
      }
    ]
  }
]


Свой раздел «Мои сервисы» с вложенными пунктами

Вариант 2. Свой раздел. Новый id с children создаёт отдельный раскрывающийся раздел (сам раздел ссылкой не является, поэтому url у него не нужен). В children добавьте столько пунктов, сколько нужно; здесь для примера — сервис на другом устройстве, поэтому указан полный адрес.

[
  {
    "id": "my-services",
    "title": { "ru": "Мои сервисы", "en": "My services" },
    "children": [
      {
        "id": "svc-1",
        "title": { "ru": "Сервис 1", "en": "Service 1" },
        "url": "http://192.168.1.50:8081",
        "isExternal": true,
        "openInNewTab": true
      },
      {
        "id": "svc-2",
        "title": { "ru": "Сервис 2", "en": "Service 2" },
        "url": "http://192.168.1.50:8082",
        "isExternal": true,
        "openInNewTab": true
      }
    ]
  }
]
Пункт «Мой сервис» в корне меню

Вариант 3. Отдельный пункт верхнего уровня. Пункт с новым id и адресом, без обёртки и children, встанет отдельной строкой в конце меню (в примере — внешний сервис с полным адресом).

[
  {
    "id": "my-service",
    "title": { "ru": "Мой сервис", "en": "My service" },
    "url": "http://192.168.1.50:8080",
    "isExternal": true,
    "openInNewTab": true,
    "requiredRole": "operator"
  }
]

Поля пункта меню:

Поле Обязательное Что задаёт
id да Идентификатор пункта. Если совпадает с идентификатором существующего пункта меню, ваши поля дополняют его — так можно, например, добавить вложенный пункт в штатный раздел. Новые пункты добавляются в конец меню.
url да, для нового пункта Адрес ссылки. Без isExternal — внутренний путь веб-интерфейса. С isExternal — либо путь от корня (/open-<имя>, /…), который следует за адресом контроллера, либо полный адрес http(s)://… для сервиса на другом устройстве.
title нет Подписи на русском и английском, достаточно одной. Без поля подписью станет id.
isExternal нет true — ссылка ведёт за пределы веб-интерфейса и открывается как обычная страница. Если адрес не подходит под требования выше, признак не действует.
openInNewTab нет Вместе с isExternal: открывать ссылку в новой вкладке.
requiredRole нет Минимальный уровень доступа, при котором пункт виден: user, operator или admin. Без поля пункт видят все.
children нет Список вложенных пунктов с такой же структурой.

Поле requiredRole управляет только видимостью пункта меню. Права доступа должен проверять сам сервис — или гейт, если ссылка ведёт на него.

Особенности и ограничения

  • Облачный сервис Wiren Board пробрасывает на контроллер только основной веб-порт, поэтому гейты работают в локальной сети или при прямом доступе к контроллеру.
  • Скрипты и программы должны сначала войти: отправить запрос POST /auth/login с именем и паролем, а затем передавать полученный cookie id в каждом запросе. Без него гейт вернет 401.
  • Если внешний порт гейта уже занят другой программой, проверка при apply этого не заметит. Если гейт не отвечает — посмотрите занятые порты командой ss -ltn и журнал веб-сервера.
  • Запросы через гейт ограничены по частоте: 15 запросов в секунду с одного IP-адреса со всплесками до 200. При устойчивом превышении гейт отвечает 429. Обычному веб-интерфейсу за гейтом этого хватает с запасом.