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

Добавил пачку новых фишек в сетевой мониторинг.
Vue Network Dashboard — плагин для Vue 3, который перехватывает весь сетевой трафик (Fetch, XHR, WebSocket, SSE) и отображает его в плавающей панели с фильтрами, статистикой и детальным просмотром запросов. Основная идея — иметь browser DevTools Network Tab внутри своего приложения, с возможностью кастомизации и программного доступа к данным.
Vue Router — маршрут на каждом запросе
Типичная ситуация: видишь в панели ошибочный запрос, но не понимаешь с какой страницы он прилетел. Особенно больно в SPA с десятками роутов — по timestamp'у угадывать неудобно.
Добавил опцию enrichWithRoute: true. Когда она включена, каждая запись в логе получает поле route — fullPath текущего маршрута в момент запроса.
typescript
app.use(NetworkDashboard, {
router,
enrichWithRoute: true,
})
В интерфейсе это даёт два изменения: поле Route в фильтр-баре (появляется только если хотя бы одна запись имеет маршрут) и чип в Meta-вкладке детального просмотра.
Важный момент про архитектуру: плагин не импортирует vue-router напрямую. Принимает любой объект с двумя полями — currentRoute.value.fullPath и afterEach. Работает в чистом Vue, Nuxt и с любым собственным роутером.
Для Nuxt — отдельный клиентский плагин:
typescript
// 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
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
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
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
// 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