Три новых раздела admin-панели: браузер базы данных, управление системой и пользователи

20.05.2026
Три новых раздела admin-панели: браузер базы данных, управление системой и пользователи

База данных

До этого, если нужно было посмотреть или поправить данные в таблице — открываешь pgAdmin, вводишь хост, пользователя, пароль, ждёшь подключения. Или пишешь одноразовый SELECT прямо в консоли. Оба пути медленные и неудобные, особенно когда нужно просто глянуть значение одного поля или исправить опечатку.

Я сделал браузер баз данных прямо внутри admin-панели.

Навигация по схемам и таблицам

Слева — дерево: сначала загружаются схемы PostgreSQL. Клик по схеме раскрывает список её таблиц — запрос идёт только при первом открытии, не при загрузке страницы. Клик по таблице открывает её содержимое справа.

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

Браузер таблицы

Открытая таблица показывает структуру и данные. Структура свёрнута по умолчанию — разворачивается кликом по строке «Структура (N колонок)». Внутри — таблица с именем, типом данных, nullable и default для каждой колонки.

Данные — 50 строк на страницу с пагинацией. Под таблицей: кнопки и , счётчик «1–50 из 247». Заголовки колонок кликабельны — сортировка по любому полю, повторный клик меняет направление, стрелка ↑↓ показывает текущее состояние.

Над таблицей — фильтр: выпадающий список колонок и поле значения. Ввёл и нажал Enter или «Найти» — данные перезагружаются с WHERE. Кнопка «✕ сброс» снимает фильтр.

Правый столбец таблицы с кнопками ✏ и ✕ закреплён: при горизонтальном скролле (а в широких таблицах он неизбежен) кнопки остаются видны. Реализовано через position: sticky; right: 0 с тенью, которая обозначает границу закреплённой части.

Редактирование отдельной ячейки

Клик по значению в ячейке открывает модальное окно с textarea. Там полное значение — без обрезки через ellipsis. Отредактировал — нажал «Сохранить», изменение применяется через PUT, таблица перезагружается.

В SQL-редакторе клик по ячейке тоже открывает это окно, но только для чтения — там нет привязки к строке, поэтому редактировать нечего.

Добавление и редактирование записей

Кнопка «+ Строка» в шапке открывает модальное окно со всеми колонками таблицы. Каждое поле подписано именем, типом данных, флагами nullable и auto. Тип данных влияет на то, какой элемент управления появляется:

  • boolean — выпадающий список true / false, плюс — null если поле nullable
  • json / jsonb — многострочное textarea
  • integer, numeric и прочие числовые — <input type="number">
  • Всё остальное — текстовое поле

Поля с nextval(...) в default помечены бейджем auto и при создании остаются пустыми — бэкенд оставляет их на усмотрение базы.

При редактировании открывается то же самое окно, но уже с заполненными значениями из выбранной строки.

Идентификация строк без первичного ключа

Удаление и обновление строк работают через системный столбец PostgreSQL ctid — физический адрес строки в файле таблицы. Бэкенд добавляет ctid::text as "__ctid" к каждому SELECT и использует его в WHERE ctid = ?::tid. Это работает для любой таблицы независимо от структуры первичного ключа.

SQL-редактор

В верхней части сайдбара — кнопка «⌨ SQL Editor». Открывает textarea с моноширинным шрифтом. Ctrl+Enter выполняет запрос. Tab вставляет два пробела вместо смены фокуса.

SELECT-запросы возвращают таблицу с результатами и счётчиком строк. INSERT / UPDATE / DELETE показывают «OK — затронуто строк: N». Ошибки отображаются с полным текстом от PostgreSQL.

История последних 15 запросов внизу — клик по строке вставляет запрос в редактор.


Система

Раздел «Система» объединяет несколько несвязанных по природе вещей, которые нужно иметь под рукой при работе с сервером.

Информация о процессе и статус сервисов

Два блока вверху страницы.

Первый — сетка плиток: uptime сервера в читаемом виде (2д 3ч 15м), версия Node.js, RSS-память, heap использованный, heap выделенный, платформа. Всё из process.memoryUsage() и process.uptime().

Второй — статус сервисов. Для PostgreSQL endpoint делает sequelize.authenticate() и измеряет время ответа. Результат — цветная точка с glow-эффектом (зелёная если ok, красная при ошибке) и latency в миллисекундах рядом с хостом. Если DB_HOST не задан — статус unknown, попытки подключиться нет.

Это полезно именно в разделе «Система», а не только на дашборде: когда идёшь сюда что-то менять или перезапускать сервер, сразу видишь состояние процесса и базы без переключения вкладок.

Фоновые задачи

Задачи делятся на два типа.

Системные задачи — встроенные в приложение. Их нельзя удалить, но можно включить/выключить и изменить интервал. Таблица показывает имя, описание, интервал в человекочитаемом виде (каждые 5 мин), время последнего запуска, статус (✓ ok / ✗ ошибка / выполняется), длительность. Кнопка ▶ запускает задачу немедленно, не дожидаясь следующего тика. Кнопка ⏸ останавливает или возобновляет. Кнопка ⚙ открывает настройки.

Webhook-задачи — пользовательские, хранятся в базе данных. По расписанию делают HTTP-запрос на указанный URL. Настраиваются: название, описание, URL, метод, заголовки (JSON), тело запроса, интервал в миллисекундах. Создание — через кнопку «+ Создать», полный CRUD. Задачи можно включать и выключать, не удаляя конфигурацию.

Оба списка обновляются автоматически каждые 10 секунд. Если только что запустил задачу вручную — увидишь результат без перезагрузки страницы.

Практический случай: нужно периодически дёргать внешний эндпоинт для прогрева кэша или синхронизации данных. Раньше — отдельный скрипт в crontab на сервере. Сейчас — webhook-задача в интерфейсе, интервал меняется на лету без деплоя.

Переменные окружения

Список всех переменных процесса, кроме системных (npm_, HOME, PATH и т.п.). Чувствительные переменные — те, в имени которых есть TOKEN, SECRET, PASSWORD, KEY и похожие — маскируются: видны первые четыре символа и многоточие. Кнопка 🔒 рядом с такой переменной делает отдельный запрос к серверу и раскрывает полное значение. Кнопка 🔓 скрывает обратно.

Поле фильтрации над таблицей — мгновенная фильтрация по имени без запросов к серверу.

Перезапуск сервера

Кнопка «Перезапустить сервер» с двухшаговым подтверждением. После клика появляется inline-подтверждение «Продолжить?» и кнопки «Да, перезапустить» / «Отмена». Подтвердил — сервер отвечает 200 и через 300 мс вызывает process.exit(0). Docker перезапустит контейнер автоматически.

Интерфейс переходит в состояние «Перезапуск… ожидаем сервер» и начинает поллинг /health раз в секунду. Когда сервер снова отвечает — показывает «✓ Сервер перезапущен» и через 4 секунды возвращается в исходное состояние.


Пользователи

До этого доступ в admin-панель контролировался двумя переменными окружения: ADMIN_UI_USERNAME и ADMIN_UI_PASSWORD. Одна учётная запись, только через переменные окружения, смена пароля — через редактирование .env и перезапуск контейнера.

Таблица admin_users и авторизация

В схеме api_admin появилась таблица admin_users: id, name, login, password_hash, created_at, updated_at. Пароли хранятся только в виде bcrypt-хешей — bcrypt.hash при создании, bcrypt.compare при логине. updated_at обновляется триггером.

Логика авторизации при логине:

  1. Если DB_HOST задан и в таблице есть хотя бы одна запись — авторизация только через базу.
  2. Если таблица пустая или база недоступна — fallback на ADMIN_UI_USERNAME / ADMIN_UI_PASSWORD из env.
  3. Если env тоже не заданы — 503.

Это позволяет запустить сервис с нуля: при первом старте, если заданы ADMIN_UI_USERNAME и ADMIN_UI_PASSWORD, сервер создаёт запись в admin_users через seedAdminUser — один раз, только если пользователя с таким логином ещё нет. Дальше работает только база, переменные окружения игнорируются.

Такой подход даёт плавную миграцию: существующая конфигурация продолжает работать без изменений, но сразу создаёт полноценную запись в базе.

JWT и актуальность данных пользователя

JWT-токен живёт 30 дней. За это время имя или логин администратора может измениться в базе — токен при этом будет нести старые данные. Перевыдавать токен при каждом изменении профиля — лишняя сложность.

Решение: endpoint GET /api/admin/me. Декодирует JWT, берёт sub (числовой id пользователя) и делает SELECT из admin_users. Клиент всегда получает актуальные данные. Если sub — строка (env-fallback токен), данные берутся из payload.

Интерфейс везде использует данные с сервера, а не из локально декодированного JWT: имя в заголовке страницы, блок «Мой аккаунт».

Список пользователей и CRUD

Таблица: имя, логин, дата создания, дата последнего обновления. Справа в каждой строке — кнопки ✏ и ✕.

Создание — модальное окно с полями: имя, логин, пароль, подтверждение пароля. Все четыре обязательны. Валидация совпадения паролей на клиенте — до отправки запроса.

Редактирование — то же окно с заполненными именем и логином. Поля пароля пустые: если оставить их пустыми — password_hash в базе не меняется. Нужно поменять только имя — пароль вводить не нужно.

Удаление — с подтверждением через confirm() с именем и логином пользователя в тексте. Деструктивное действие, которое выполняется редко — нативный диалог здесь достаточен и надёжен.

Мой аккаунт и смена пароля

В начале страницы — блок «Мой аккаунт» с именем и логином текущего пользователя. Данные берутся с сервера через /api/admin/me, не из JWT.

Кнопка «Сменить пароль» открывает модальное окно с тремя полями: текущий пароль, новый пароль, подтверждение. Логика смены:

  1. Верификация текущего пароля — повторный логин через POST /api/admin/login с текущим паролем.
  2. Если логин успешен — PUT /api/admin/users/:id с новым паролем.

Отдельного endpoint для смены пароля нет. Используется тот же путь, что и при обычном логине — те же правила проверки, никакого отдельного кода, который нужно поддерживать.


Все три раздела работают под тем же adminUiAuth middleware. Отдельного права «только для суперадмина» нет — любой пользователь из admin_users имеет полный доступ ко всему, включая управление другими пользователями. Для проекта с небольшой командой это нормально; если потребуются роли — их можно добавить позже без изменения архитектуры.

Читать далее

21.05.2026

Файловый менеджер в admin-панели

В admin-панель добавил полноценный файловый менеджер — браузер файловой системы с редактором кода на CodeMirror 6, подсветкой синтаксиса, просмотром изображений, операциями копирования и переноса, загрузкой папок целиком через drag-and-drop и групповыми действиями над несколькими файлами.

Метки
admin-panelfile-managercodemirrornode.jsvue
21.05.2026

Терминал в браузере: вкладки, история команд и избранное прямо в admin-панели

Добавил полноценный веб-терминал в admin-панель — интерактивная bash-сессия прямо в браузере, до пяти вкладок одновременно, история и избранное с поиском, состояние сохраняется при переключении между разделами.

Метки
terminaladmin-panelweb-terminalbashdeveloper-tools
22.05.2026

@macrulez/vue-form-schema: реактивные формы из схемы для Vue 3

Написал пакет, который избавляет от бойлерплейта при работе с формами во Vue 3: описываешь поля в виде массива объектов — получаешь готовую валидацию, маскирование, условное отображение и автоматический рендеринг.

Метки
vue3formsvalidationtypescriptcomposable