Спортзал: расписание на экранах в зале с локального сервера, а не из чужого личного кабинета
В любом фитнес-клубе с групповыми программами есть экран — у ресепшена, у входа в зал, в холле перед раздевалкой. На нём расписание: что идёт сейчас, что через полчаса, кто ведёт, в каком зале. Для клиента это первая витрина клуба — по ней он решает, куда идти и стоит ли ждать. И почти всегда за этим экраном стоит не свой сервер, а чужой личный кабинет: клуб арендует у сервиса онлайн-записи виджет расписания и просто открывает его в браузере на ТВ-боксе, подключённом к экрану. Пока сервис работает — всё гладко. Как только у него сбой, плановые работы или медленный ответ API — экран превращается в белый лист, крутящийся спиннер или страницу логина, и это видят все, кто в этот момент стоит в зале с полотенцем на плече. Дальше — как вынести именно отображение расписания на свой небольшой сервер, оставив саму запись клиентов там же, где она была, но избавив витрину в зале от зависимости от доступности чужого личного кабинета.
Содержание
- Почему экран с расписанием — это не украшение, а рабочий инструмент
- Как это обычно устроено технически
- Что реально происходит при сбое чужого сервиса
- Идея: расписание живёт у вас, личный кабинет — только источник данных
- Как это собрать технически
- Клиентское устройство: браузер в режиме киоска
- Что это реально даёт клубу
Почему экран с расписанием — это не украшение, а рабочий инструмент
В клубе с десятком групповых направлений экран решает конкретную задачу: снимает с администратора необходимость отвечать на один и тот же вопрос по двадцать раз в час — «а во сколько сегодня йога», «а где сегодня функционалка, в первом зале или во втором». Клиент подходит, смотрит, идёт. Тренер видит на экране в холле, что после его класса идёт растяжка, и не задерживает группу. Новый посетитель по абонементу ориентируется по залу без разговора с администратором вообще.
Когда экран замолкает, вся эта нагрузка мгновенно возвращается на ресепшен — причём в вечерний час пик, когда там и так очередь на оплату и переобувание. Плюс репутационный момент: клиент, который пришёл в клуб впервые и упёрся в белый экран или страницу с ошибкой авторизации стороннего сервиса, делает вывод не про сервис записи, а про сам клуб — «у них тут вечно что-то не работает».
Как это обычно устроено технически
Типовая связка: телевизор или монитор в зале, подключённый к нему Android TV бокс или мини-ПК, на нём браузер в режиме киоска, который открывает публичную страницу расписания сервиса онлайн-записи — либо встроенный виджет, либо просто открытая вкладка личного кабинета администратора. Иногда это буквально забытая открытая сессия: кто-то из персонала вошёл в личный кабинет, вывел вкладку на экран и больше не трогал — пока сессия не протухнет по таймауту, и вместо расписания не всплывёт форма входа.
Сервис записи в этой схеме хорош ровно для того, для чего он создан — записи и оплаты, синхронизации тренеров, работы с абонементами. Он не проектировался как источник постоянно доступной вывески для чужого зала, и никакой SLA на этот сценарий использования никто не даёт. Похожая логика уже разбиралась применительно к CRM: сама подписка на чужой сервис против своего сервера экономически может быть оправдана для записи и учёта, но это не значит, что от неё же должна зависеть каждая витрина в помещении.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧто реально происходит при сбое чужого сервиса
Список не гипотетический, а вполне бытовой:
- Плановые технические работы у сервиса записи, чаще всего ночью или в выходные — экран в этот момент просто не грузится или показывает служебную страницу.
- Истечение сессии в открытой вкладке личного кабинета — вместо расписания экран показывает форму входа с логином и паролем администратора клуба, что заодно и не очень безопасно демонстрировать всем посетителям.
- Недоступность API виджета при скачке нагрузки на стороне сервиса — у крупных SaaS-провайдеров записи в пиковые часы (обычно те же вечерние часы, что и у фитнес-клубов) случаются замедления и таймауты.
- Проблемы с интернетом в самом клубе — если виджет тянется напрямую из облака, любой обрыв или деградация канала у клуба сразу гасит экран, даже если у сервиса записи всё хорошо.
- Изменение или прекращение работы сервиса — провайдер меняет тарифы, ограничивает бесплатный виджет, закрывается или переезжает на новый домен, и экран в зале узнаёт об этом первым.
Ключевая проблема не в частоте этих событий, а в том, что клуб не может ничего с ними сделать. Это чужая инфраструктура, у клуба нет доступа чинить её быстрее, чем провайдер сам решит проблему.
Идея: расписание живёт у вас, личный кабинет — только источник данных
Правильная граница ответственности здесь такая: запись клиентов, оплата абонементов, работа тренеров с личными кабинетами — всё это можно и дальше вести в привычном сервисе, мигрировать туда ничего не нужно. А вот отображение расписания на экранах в зале выносится на свой небольшой сервер, который держит у себя актуальную копию расписания и отдаёт её на экраны напрямую, по локальной сети или по короткому маршруту в интернете, не завися от того, отвечает ли в этот момент личный кабинет сервиса записи.
Устроено это просто: сервер периодически забирает расписание у сервиса записи (через API, экспорт или календарный фид — способ зависит от тарифа провайдера) и сохраняет последнюю успешно полученную версию у себя. Экраны в зале запрашивают расписание не у стороннего SaaS, а у собственного сервера. Если запрос к сервису записи не удался — сервер просто продолжает отдавать последнюю сохранённую версию, а не отключает витрину. Похожий принцип «расписание отдельно, источник записи отдельно» уже применялся для другой отрасли: автошкола решала эту же задачу для расписания инструкторов, и там разбирался тот же самый переход — расписание инструкторов на своём сервере вместо прямой завязки на чужой кабинет.
Возможны два варианта того, откуда берётся расписание:
Вариант А — синхронизация из сервиса записи. Свой сервер раз в 15–60 минут забирает данные у SaaS (экспорт, API или ICS-календарь, если сервис его отдаёт) и кладёт в кэш. Плюс — администратор ничего не меняет в привычной работе, ведёт запись как обычно. Минус — расписание на экране может на короткое время отставать от факта записи, а если у сервиса нет ни API, ни экспорта, синхронизацию приходится собирать вручную.
Вариант Б — своя панель как источник истины для экрана. Администратор сам вносит расписание в простую собственную панель (форма с залом, временем, направлением, тренером), а сервис записи используется отдельно, только для приёма оплаты и записи клиентов. Плюс — экран вообще не зависит от доступности сервиса записи, даже если тот полностью недоступен несколько дней. Минус — двойной ввод данных, что для клуба с частыми заменами тренеров неудобно.
На практике для большинства клубов рабочий компромисс — вариант А с локальным кэшем и явным индикатором времени последнего обновления на самом экране, а вариант Б держать в уме как запасной путь, если у сервиса записи в принципе нет способа выгрузить расписание автоматически.
Как это собрать технически
Держать сервер физически в подсобке клуба не обязательно — удобнее арендовать небольшой VPS: не нужно чинить железо на месте, есть нормальные бэкапы, доступ для настройки из любой точки, и такой сервер годится сразу на несколько филиалов клуба, если сеть небольшая. Дальше — минимальный набор из трёх частей: веб-сервер, отдающий страницу для экрана, скрипт синхронизации с сервисом записи и клиент-браузер в режиме киоска на самом ТВ-боксе.
Пример docker-compose.yml для сервера:
version: "3.8"
services:
web:
image: nginx:alpine
volumes:
- ./public:/usr/share/nginx/html:ro
- ./data:/usr/share/nginx/html/data:ro
ports:
- "80:80"
restart: unless-stopped
sync:
build: ./sync
volumes:
- ./data:/data
environment:
- SOURCE_URL=https://ваш-сервис-записи/export
- SOURCE_TOKEN=${SOURCE_TOKEN}
restart: unless-stopped
Сама страница расписания — статический HTML с JS, который раз в минуту дотягивает data/schedule.json и перерисовывает таблицу, а при неудаче просто оставляет старые данные на экране:
async function refresh() {
try {
const res = await fetch('/data/schedule.json', { cache: 'no-store' });
if (!res.ok) throw new Error(res.status);
const data = await res.json();
render(data);
document.getElementById('updated').textContent =
'Обновлено: ' + new Date(data.generated_at).toLocaleTimeString('ru-RU');
} catch (e) {
// сеть или сервер недоступны — не трогаем то, что уже нарисовано
console.warn('Обновление не удалось, показываем последние данные', e);
}
}
setInterval(refresh, 60000);
refresh();
Скрипт синхронизации — отдельный маленький сервис или просто cron-задача, которая тянет расписание у провайдера записи и пишет schedule.json только при успешном ответе, не затирая файл при ошибке:
#!/usr/bin/env bash
set -e
tmp=$(mktemp)
if curl -fsS -H "Authorization: Bearer $SOURCE_TOKEN" "$SOURCE_URL" -o "$tmp"; then
mv "$tmp" /data/schedule.json
echo "$(date -Is) sync ok" >> /data/sync.log
else
echo "$(date -Is) sync FAILED, keeping previous file" >> /data/sync.log
fi
Логику расписания cron-задач и типичные грабли с ними (пропущенный запуск, наложение задач, поведение при переводе часов) разобраны отдельно в статье про настройку cron-задач с примерами — для синхронизации раз в 15–30 минут это прямо применимо.
Клиентское устройство: браузер в режиме киоска
На ТВ-боксе или мини-ПК у экрана достаточно браузера, открытого без адресной строки и без возможности случайно его закрыть:
chromium-browser --kiosk --noerrdialogs \
--disable-session-crashed-bubble --disable-infobars \
--autoplay-policy=no-user-gesture-required \
"http://schedule.клуб.local/?room=zal1"
Параметр ?room=zal1 в URL — простой способ показывать на каждом экране только своё: у входа в зал функциональных тренировок один вид, у ресепшена — сводное расписание на весь день по всем залам. Обрабатывается это на самом сервере при генерации schedule.json или прямо в JS фильтрацией по параметру.
Практические нюансы, которые обычно всплывают уже после запуска:
- Браузер зависает за несколько дней работы без перезапуска — держите его под systemd с
Restart=alwaysи добавьте ночную перезагрузку самого устройства через cron, когда клуб закрыт. - Проводная сеть надёжнее Wi-Fi для экрана, который висит на одном месте годами — если есть возможность кинуть кабель, лучше не полагаться на беспроводную точку в зале с десятками телефонов клиентов рядом.
- ТВ уходит в спящий режим по собственным настройкам энергосбережения — это нужно отключить отдельно в настройках самого телевизора, режим киоска в браузере тут не поможет.
- Часовой пояс на устройстве должен совпадать с часовым поясом клуба, иначе расписание технически верное, но сдвинутое по времени показа.
Что это реально даёт клубу
- Независимость витрины от доступности чужого личного кабинета — сбой у провайдера записи больше не гасит экраны в зале.
- Контроль над внешним видом — свой логотип, цвета клуба, без чужого брендинга и рекламных элементов виджета.
- Возможность добавить своё — акции, свободные места в ближайшем классе, QR-код на запись пробного занятия — без ограничений тарифа стороннего сервиса.
- Быстрая ручная правка — если тренер заболел и класс отменяется прямо сейчас, администратор может поправить расписание на экране сразу через свою панель, не дожидаясь, пока изменение долетит через синхронизацию с CRM.
- Честное ограничение, которое стоит проговорить с администраторами сразу: при синхронизации раз в 15–30 минут экран может на короткое время отставать от факта в системе записи. Для устного объявления об отмене класса это не заменяет — тренер всё равно предупреждает голосом, экран лишь дублирует информацию для тех, кто зашёл в зал позже.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужно ли переносить саму запись клиентов на свой сервер?
Нет. Запись, оплата и работа тренеров остаются в привычном сервисе — на свой сервер выносится только отображение расписания на экранах.
Что делать, если у сервиса записи вообще нет API или экспорта?
Тогда остаётся вариант Б: администратор вносит расписание в простую собственную панель вручную, раз в день или при изменениях — это дополнительная рутина, но экран перестаёт зависеть от чужой инфраструктуры полностью.
Хватит ли слабого сервера для такой задачи?
Да, отдача статической страницы и небольшого JSON-файла — минимальная нагрузка, даже на несколько филиалов и десяток экранов одновременно ресурсов нужно немного.
Что если сломается сам сервер клуба, а не сервис записи?
Риск не исчезает, а смещается — но теперь он под вашим контролем: можно держать резервную копию schedule.json и конфигурации, поднять сервер заново за минуты, а не ждать восстановления работы стороннего провайдера.
Как быть с несколькими залами или филиалами клуба?
Один и тот же сервер обслуживает все экраны сразу, разделяя вывод параметром зала или филиала в URL — отдельная инфраструктура под каждую точку не нужна.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →