<divclass="toolbar"><divclass="segments"><buttonclass="active"data-kind="">Весь трафик</button><buttondata-kind="request">HTTP</button><buttondata-kind="alert">Срабатывания</button><buttondata-kind="decision">Решения</button></div><selectid="domain"aria-label="Фильтр по домену"><optionvalue="">Все домены</option></select><aid="networkLink"href="/network"style="font-size:10px;white-space:nowrap"hidden>TCP / UDP ↗</a><labelid="externalControl"><inputtype="checkbox"id="external"> Только внешние IP</label><labelid="monitoringControl"><inputtype="checkbox"id="hideMonitoring"> Скрыть мониторинг</label><labelid="networkOverlayControl"hidden><inputtype="checkbox"id="networkOverlay"checked> Сеть на карте</label><buttonid="pause">Пауза</button></div>
<sectionclass="metrics"aria-label="Статистика за последние 24 часа"><buttonclass="metric"data-metric="unique"><div><small>Уникальные IP · 24 ч</small><bid="unique">—</b></div><iaria-hidden="true">⌁</i></button><buttonclass="metric request"data-metric="requests"><div><small>HTTP-запросы · 24 ч</small><bid="requests">—</b></div><iaria-hidden="true">↗</i></button><buttonclass="metric alert"data-metric="alerts"><div><small>Срабатывания · 24 ч</small><bid="alerts">—</b></div><iaria-hidden="true">ϟ</i></button><buttonclass="metric decision"data-metric="decisions"><div><small>Решения CrowdSec · 24 ч</small><bid="decisions">—</b></div><iaria-hidden="true">◇</i></button></section>
<sectionclass="dashboard"><divclass="panel map-panel"><divclass="map-heading"><spanclass="section-label">Глобальная активность</span><buttonclass="source-status"id="sourceDetails"aria-label="Состояние источника"><spanid="sourceName">Источник…</span><spanid="status"class="status-box"role="status">Подключение</span><span>ⓘ</span></button></div><divclass="map-stage"><buttonid="networkSummary"hiddenaria-label="Сетевая активность на карте"></button><divclass="map-controls"><buttonid="zoomOut"aria-label="Отдалить карту">−</button><outputid="zoomLevel">100%</output><buttonid="zoomIn"aria-label="Приблизить карту">+</button><buttonid="zoomReset"aria-label="Сбросить масштаб карты">↺</button></div><canvastabindex="0"id="world"role="img"aria-label="Карта стран источников; данные доступны также в статистике и ленте событий"></canvas></div><divclass="map-foot"><divclass="legend"><span><iclass="dot"></i>HTTP</span><span><iclass="dot alert"></i>Срабатывание</span><span><iclass="dot decision"></i>Решение</span></div><spanid="unmapped">Без геолокации: —</span></div></div>
<sectionclass="dashboard"><divclass="panel map-panel"><divclass="map-heading"><spanclass="section-label">Глобальная активность</span><buttonclass="source-status"id="sourceDetails"aria-label="Состояние источника"><spanid="sourceName">Источник…</span><spanid="status"class="status-box"role="status">Подключение</span><span>ⓘ</span></button></div><divclass="map-stage"><buttonid="networkSummary"hiddenaria-label="Сетевая активность на карте"></button><divclass="map-controls"><buttonid="zoomOut"aria-label="Отдалить карту">−</button><outputid="zoomLevel">100%</output><buttonid="zoomIn"aria-label="Приблизить карту">+</button><buttonid="zoomReset"aria-label="Сбросить масштаб карты">↺</button></div><canvastabindex="0"id="world"role="img"aria-label="Карта стран источников; данные доступны также в статистике и ленте событий"></canvas></div><divclass="map-foot"><divclass="legend"><span><iclass="dot"></i>HTTP</span><span><iclass="dot alert"></i>Срабатывание</span><span><iclass="dot decision"></i>Решение</span></div><spanid="cityLayerNote"title="Города по приблизительным координатам GeoIP"></span><spanid="unmapped">Без геолокации: —</span></div></div>
<asideclass="panel feed-panel"><divclass="feed-heading"><h2>Поток событий</h2><spanclass="note"id="feedMode">LIVE / 100</span></div><divclass="feed"id="feed"><divclass="empty">Ожидание первого события…</div></div><divclass="pager"><buttonid="feedPrev"aria-label="Предыдущая страница событий">←</button><spanid="feedPage">—</span><buttonid="feedNext"aria-label="Следующая страница событий">→</button><buttonid="latest">К новым ↗</button></div></aside></section>
<sectionclass="bottom"><divclass="panel chart-panel"><divclass="bottom-head"><h2>Ритм запросов <spanclass="note">/ 60 мин</span></h2><spanclass="note"><bid="qps"style="color:var(--blue)">—</b> req/s</span></div><canvasclass="chart"id="chart"role="img"aria-label="HTTP-запросы в минуту за последний час; req/s — среднее за последние 60 секунд"></canvas></div><divclass="panel"><divclass="bottom-head"><h2>География</h2><spanclass="note">ТОП 7 / события</span></div><divid="countries"></div></div><divclass="panel"><divclass="bottom-head"><h2>Источники</h2><spanclass="note">ТОП 7 / IP</span></div><divid="ips"></div></div></section>
<footer><divclass="credits"><span><ahref="https://db-ip.com"target="_blank"rel="noopener">IP Geolocation by DB-IP</a> · <spanid="geoDate"></span></span><aclass="license-credit"href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a><aclass="natural-credit"href="https://www.naturalearthdata.com/">Natural Earth</a></div><buttonclass="footer-help"id="about">Как читать данные ⓘ</button><ahref="/#traffic-settings">Настройки ↗</a></footer>
@@ -52,9 +52,11 @@ function networkVisible(){return !!view?.network&&el('networkOverlay').checked&&
ctx.globalAlpha=1;ctx.strokeStyle=colors.decision;ctx.lineWidth=1.5/mapZoom;ctx.beginPath();ctx.arc(...end,7/mapZoom,0,Math.PI*2);ctx.stroke();ctx.fillStyle=isLight()?'#554496':'#e7dcff';ctx.beginPath();ctx.arc(...end,3/mapZoom,0,Math.PI*2);ctx.fill();ctx.font=(11/mapZoom)+'px system-ui';ctx.textAlign='center';ctx.fillText(destination?'NPM · '+destination.name:'NPM · геопозиция не задана',end[0],end[1]+22/mapZoom);
}
ctx.restore();
@@ -70,7 +72,7 @@ function render(data){view=data;el('networkLink').hidden=!data.settings.networkE
constunmapped=data.countries.filter(c=>!countryMap.has(c.name)).reduce((n,c)=>n+c.count,0);el('unmapped').textContent='Локальные / без геолокации: '+num(unmapped);
functionrenderNetworkSummary(){el('networkSummary').hidden=!networkVisible();if(!networkVisible())return;constn=view.network,fresh=networkFresh();el('networkSummary').innerHTML=(fresh?'Сеть · ':'Сеть · нет свежего снимка · ')+['tcp','udp','icmp'].map(p=>'<span style="color:'+colors[p]+'">'+p.toUpperCase()+' '+num(n[p])+'</span>').join(' · ')+(n.error||n.limited?' · ⚠':'')+' ⓘ';el('networkSummary').title='Текущие потоки conntrack. Фильтры домена и HTTP/CrowdSec на них не влияют.'}
el('networkSummary').onclick=()=>{constn=view.network;showDetails('Сетевая активность на карте','<p>Зелёный — TCP, розовый — UDP, жёлтый — ICMP/ICMPv6. Кольца показывают страны текущих потоков, импульс означает получение нового снимка (~5 с), а не новый запрос или пакет.</p><p>Входящий поток идёт к NPM, исходящий — от NPM. Если оба конца публичные, направление определяется по инициатору; это не трассировка маршрута.</p><p>HTTP-фильтры домена и типа события относятся только к HTTP/CrowdSec. «Только внешние IP» и «Скрыть мониторинг» применяются также к сетевому слою. Для сети используются только правила без домена/пути.</p><p>Снимок: '+(n.at?newDate(n.at*1000).toLocaleString('ru-RU'):'ещё не получен')+'. Локальные / без страны: '+num(n.unmapped)+'. Скрыто потоков мониторинга: '+num(n.monitoringHidden)+'.</p><p>'+safe(n.error||(n.limited?'Достигнут лимит 5 000 потоков':'При устаревании снимка сетевые кольца и импульсы скрываются.'))+'</p><p><a href="/network">Открыть таблицу TCP/UDP/ICMP ↗</a></p>')};
el('about').onclick=()=>showDetails('Как читать данные','<p>Голубой — HTTP-запросы, оранжевый — срабатывания CrowdSec, сиреневый — решения о ban или captcha.</p><p>Решение о бане не подтверждает конкретный отброшенный запрос. HTTP 401/403/502 сами по себе не означают блокировку CrowdSec.</p><p>Счётчики за 24 часа следуют выбранным фильтрам. Уникальный IP не равен человеку. Req/s — среднее за 60 секунд; график показывает запросы в минуту за последний час.</p><p>Точки на карте обозначают страны, а не точное местоположение людей. Локальные адреса остаются в ленте без страны. «Скрыть мониторинг» исключает из отображения HTTP-проверки по правилам в настройках; события CrowdSec сохраняются на экране. Исходная история не удаляется. В ленте показаны последние 100 событий; полная сохранённая история за 24 часа доступна в карточках и окнах стран; нажмите строку, чтобы увидеть детали целиком.</p>');
el('about').onclick=()=>showDetails('Как читать данные','<p>Голубой — HTTP-запросы, оранжевый — срабатывания CrowdSec, сиреневый — решения о ban или captcha.</p><p>Решение о бане не подтверждает конкретный отброшенный запрос. HTTP 401/403/502 сами по себе не означают блокировку CrowdSec.</p><p>Счётчики за 24 часа следуют выбранным фильтрам. Уникальный IP не равен человеку. Req/s — среднее за 60 секунд; график показывает запросы в минуту за последний час.</p><p>При масштабе от 200% и установленной базе городов появляются города с HTTP/CrowdSec-активностью. Геолокация приблизительная. Сетевые потоки и события без города остаются на уровне стран. Показываются до 500 наиболее активных городов; остальные события учитываются в точках стран. Локальные адреса остаются в ленте без страны. «Скрыть мониторинг» исключает из отображения HTTP-проверки по правилам в настройках; события CrowdSec сохраняются на экране. Исходная история не удаляется. В ленте показаны последние 100 событий; полная сохранённая история за 24 часа доступна в карточках и окнах стран; нажмите строку, чтобы увидеть детали целиком.</p>');
functionopenCity(c){showDetails(c.city,filterNote()+'<p>'+safe([countryName(c.country),c.region].filter(Boolean).join(' · '))+'</p>'+statGrid(c)+'<p class="modal-note">Приблизительное местоположение IP по DB-IP. Это может быть город провайдера или VPN, а не местоположение посетителя.</p><p>Последнее событие: '+newDate(c.lastSeen*1000).toLocaleString('ru-RU')+'</p><button class="country-choice" data-country="'+safe(c.country)+'">Вся история страны: '+safe(countryName(c.country))+'</button>')}
document.querySelectorAll('[data-metric]').forEach(b=>b.onclick=()=>{if(!view)return;constkey=b.dataset.metric,title={unique:'Уникальные HTTP IP',requests:'HTTP-запросы',alerts:'Срабатывания CrowdSec',decisions:'Решения CrowdSec'}[key],field=key==='unique'?'uniqueIps':key,type={unique:'request',requests:'request',alerts:'alert',decisions:'decision'}[key],rows=(view.countryDetails||[]).filter(c=>c[field]>0).sort((a,b)=>b[field]-a[field]);constexplanation={unique:'Уникальный IP не равен человеку. Учитываются IP HTTP-запросов.',requests:'Количество HTTP-запросов. Ошибки HTTP сами по себе не доказывают атаку.',alerts:'Срабатывания сценариев CrowdSec. Одно срабатывание может объединять несколько запросов.',decisions:'Решения CrowdSec, например ban или captcha. Это не число отброшенных пакетов и не список активных банов.'}[key];showDetails(title,filterNote()+'<div class="detail-stat"><small>'+title+'</small><b>'+num(view[field])+'</b></div><p>'+explanation+'</p><h3>По странам</h3>'+rows.map(c=>'<button class="country-choice" data-country="'+safe(c.country)+'">'+safe(countryName(c.country))+' · '+num(c[field])+'</button>').join('')+historyShell());startHistory({type,group:key==='unique'?'ip':''})});
r,err:=s.db.Query(`SELECT CAST((ts/60)*60 AS TEXT),COUNT(*) FROM traffic_events`+where+` AND kind='request' AND ts>=? GROUP BY ts/60 ORDER BY ts/60`,append(args,now-3600)...)
rows,err:=s.db.Query(`SELECT ip,country,SUM(kind='request'),SUM(kind='alert'),SUM(kind='decision'),MAX(ts) FROM traffic_events`+where+` GROUP BY ip,country`,args...)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.