vue-network-dashboard — большое обновление фильтров, инспектора и панели статистики

13.04.2026
vue-network-dashboard — большое обновление фильтров, инспектора и панели статистики

Добавил пачку новых фишек в сетевой мониторинг.

Vue Network Dashboard — плагин для Vue 3, который перехватывает весь сетевой трафик (Fetch, XHR, WebSocket, SSE) и отображает его в плавающей панели с фильтрами, статистикой и детальным просмотром запросов. Основная идея — иметь browser DevTools Network Tab внутри своего приложения, с возможностью кастомизации и программного доступа к данным.


Vue Router — маршрут на каждом запросе

Типичная ситуация: видишь в панели ошибочный запрос, но не понимаешь с какой страницы он прилетел. Особенно больно в SPA с десятками роутов — по timestamp'у угадывать неудобно.

Добавил опцию enrichWithRoute: true. Когда она включена, каждая запись в логе получает поле routefullPath текущего маршрута в момент запроса.

typescript Copy
app.use(NetworkDashboard, {
  router,
  enrichWithRoute: true,
})

В интерфейсе это даёт два изменения: поле Route в фильтр-баре (появляется только если хотя бы одна запись имеет маршрут) и чип в Meta-вкладке детального просмотра.

Важный момент про архитектуру: плагин не импортирует vue-router напрямую. Принимает любой объект с двумя полями — currentRoute.value.fullPath и afterEach. Работает в чистом Vue, Nuxt и с любым собственным роутером.

Для Nuxt — отдельный клиентский плагин:

typescript Copy
// plugins/network-dashboard.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(NetworkDashboardPlugin, {
    router: nuxtApp.$router,
    enrichWithRoute: true,
  })
})

Regex-поиск в фильтрах

Фильтры по URL, Body и Route теперь понимают regex:. Пишешь regex:\/api\/user\/\d+ — и фильтруются только запросы к конкретным user id.

Если строка начинается с regex:, берётся всё после него как RegExp. Если выражение невалидное — фильтр просто ничего не находит, без ошибок в консоли.

В интерфейсе появляется бейдж «RX» справа от поля и лёгкая подсветка фона — чтобы было видно, что активен режим регулярок.


Подсветка совпадений в URL

Когда фильтр по URL активен — совпадение подсвечивается прямо в строке лога. Amber-акцент, виден без раскрытия детальной панели.

Работает через computed, который разбивает URL на части { text, match }[]:

typescript Copy
while ((m = rx.exec(url)) !== null) {
  if (m.index > lastIndex)
    parts.push({ text: url.slice(lastIndex, m.index), match: false })
  parts.push({ text: m[0], match: true })
  lastIndex = rx.lastIndex
}

В шаблоне — v-for по частям, класс .url-match на тех у кого match: true. Работает и с подстрокой, и с regex:.


Модалка экспорта

Раньше экспорт был одной кнопкой: нажал — скачал JSON. Теперь — полноценный попап с настройками.

Что внутри:

  • Формат: JSON, CSV, HAR
  • Протоколы: чекбоксы HTTP / WS / SSE (появляются только если в логах есть хотя бы два разных типа)
  • Статусы: 2xx / 3xx / 4xx / 5xx
  • Временной диапазон: два ползунка, показывают N / M записей в реальном времени

HAR-формат открывается напрямую в Chrome DevTools или Postman. Удобно когда хочешь поделиться сессией с коллегой или разобраться в проблеме в другом инструменте.


HAR-импорт

Кнопка «Import» в хедере. Загружает .har файл и показывает его записи в том же интерфейсе — с теми же фильтрами, детальной панелью, статистикой. HAR-записи конвертируются в UnifiedLogEntry, разницы в UX никакой.

Пока активен импорт — вверху появляется баннер с именем файла и количеством записей. Кнопка «×» возвращает к живому трафику.

Сценарий: поймал проблему, экспортировал сессию как HAR, отправил коллеге. Он загружает файл и видит ровно то же что видел ты — все детали запросов и ответов, в привычном интерфейсе.


Copy as cURL

Кнопка в детальной панели HTTP-запроса. Копирует в буфер обмена готовую команду curl — метод, все заголовки, тело. Служебные заголовки (host, content-length, transfer-encoding, connection, keep-alive) исключаются автоматически — они не нужны при ручном вызове и ломают некоторые серверы.

bash Copy
curl -X POST 'https://api.example.com/users' \
  -H 'content-type: application/json' \
  -H 'x-request-id: abc123' \
  -d '{"name":"Alice"}'

Вставил в терминал, запустил, посмотрел ответ.


Replay запроса

Кнопка рядом с cURL. Повторяет запрос с теми же заголовками и телом через fetch. Повторный запрос перехватывается интерцептором и появляется как новая запись в логе — можно сразу сравнить две в Diff-режиме.

Хороший сценарий: поймал медленный запрос, нажал Replay несколько раз — смотришь разброс времён без лишних телодвижений.


Полноэкранный режим

Иногда панель 960×620 мала — много запросов, длинные URL, хочется видеть больше. Кнопка полного экрана растягивает панель на весь вьюпорт, Escape возвращает к прежнему размеру и позиции.

Нюанс, который пришлось решить: страница под панелью продолжала скроллиться при прокрутке списка логов. Решается через document.body.style.overflow = 'hidden' при входе и сбросом при выходе. Просто, но важно не забыть снять при onUnmounted.


Виртуальный скролл

При 500+ записях — все строки в DOM одновременно, ощутимые тормоза при активных фильтрах.

Решение без внешних зависимостей: IntersectionObserver на sentinel-элементе в конце списка. Первые 100 строк, при скролле до sentinel'а — ещё 50. При смене фильтров счётчик сбрасывается на 100.

typescript Copy
scrollObserver = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting && displayCount.value < filteredLogs.value.length)
    displayCount.value = Math.min(displayCount.value + 50, filteredLogs.value.length)
})

Sentinel — div высотой 1px в конце списка. Попал в viewport — подгрузилась следующая порция.


Дебаунс фильтров

Текстовые поля (URL, Body, Route) обновляли filteredLogs при каждом нажатии клавиши. При 1000+ записях каждый символ перебирал весь массив.

Два ref-а: filters — то, что видит UI, и activeFilters — то, что реально используется в filteredLogs. Watch на filters обновляет нетекстовые поля (Method, Status, тип, длительность, ошибки) сразу, а текстовые — через setTimeout 180ms.

Reset очищает оба состояния одновременно, без задержки.


Сохранение фильтров в sessionStorage

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

Теперь активные фильтры сохраняются в sessionStorage под ключом vue-network-dashboard:filters. При монтировании компонента — читаются и восстанавливаются. Reset сбрасывает и состояние, и хранилище.

В рамках одной вкладки фильтры живут столько, сколько живёт сессия. Открыл новую вкладку — чистый старт.


Фильтр WS-сообщений

WebSocket-записи в логе — это цепочка событий: connection, open, множество message, иногда error, close. При высокочастотных соединениях (realtime, финансовые данные, игры) сигнал теряется в шуме lifecycle-событий.

Когда в фильтр-баре выбран тип WS — появляется дополнительный тогл «Messages only». Включил — скрыты все записи кроме eventType === 'message'. Видишь только данные, без протокольного шума.


Спарклайн трафика

В Statistics-вкладке добавился SVG-чарт — количество запросов по временной оси. 40 бакетов по 5 секунд, последние ~3 минуты. Видно burst, пик нагрузки, затихание.

Реализован без внешних зависимостей: <polyline> с вычисленными точками. Пересчёт — чистый computed.

Нетривиальный баг при реализации: при ресайзе панели форма графика менялась, причём сильно. Причина — Date.now() вызывался внутри computed, который зависел от ширины контейнера. При ресайзе computed пересчитывался → Date.now() возвращал новое время → временное окно сдвигалось → точки попадали в другие бакеты → форма менялась.

Решение — разбить на два computed:

typescript Copy
// Date.now() только здесь — зависит только от logs
const bucketCounts = computed(() => {
  const now = Date.now()
  const from = now - MAX_BUCKETS * BUCKET_MS
  // ... группировка по бакетам
})

// зависит от bucketCounts + ширины — Date.now() не вызывается
const sparkline = computed(() => {
  const counts = bucketCounts.value
  const w = sparklineWidth.value
  // ... генерация points для SVG polyline
})

При ресайзе bucketCounts берётся из кеша, Date.now() не вызывается. Форма не меняется — пересчитываются только x-координаты точек.

sparklineWidth следит за контейнером через ResizeObserver. При любом изменении размера панели, включая переход в fullscreen, ширина обновляется и SVG viewBox подстраивается.


Всё это — без единой внешней зависимости, кроме Vue 3. Интерцептор, виртуальный скролл, граф трафика — стандартные браузерные API и реактивность Vue.

NPM: https://www.npmjs.com/package/vue-network-dashboard
GitHub: https://github.com/macrulezru/vue-network-dashboard

Читать далее

12.04.2026

vue-i18n-kit 0.4.0 — редактируй переводы прямо в приложении

В версии 0.4.0 главная фича — in-context editing: карандашик рядом с каждой репликой в dev-режиме, попап-редактор прямо на странице и полноценный UI во встроенном iframe — без переключения вкладок. Плюс namespace splitting, translation memory и поддержка i18n Ally.

Метки
vuei18nlocalizationvitein-context-editing
11.04.2026

vue-i18n-kit 0.3.0 — TypeScript типы, устаревшие переводы, XLIFF/PO, DeepL и отчёт по покрытию

Крупное обновление инструментов локализации для Vue 3: генерация TypeScript-типов из ключей, детектор устаревших переводов, экспорт и импорт XLIFF/PO для переводчиков, поддержка DeepL, CLI-отчёт по покрытию и улучшенный дашборд.

Метки
vue3i18nlocalizationvue-i18nclideveloper-tools