feat(web): второй ряд дашборда и пинг участников в голосовой комнате

- дашборд инстанса: два ряда полукругов с подписями сверху — «Контейнер
  инстанса» (лимиты профиля) и «Сервер целиком» (вся машина);
- голосовая комната: рядом с качеством связи видно пинг участника
  («Отличное соединение, 42 мс») и он обновляется раз в секунду. Свой RTT
  измеряется по статистике WebRTC, чужой приходит по data-каналу LiveKit
  (`glchat.latency`, lossy), поэтому пинг виден для каждого участника;
- статистика берётся с любой своей дорожки: в аудио-сессии RTT раньше был
  недоступен (отчёт читался только с видео);
- в «Аудио-видео» появился тумблер «Показывать пинг в голосовой комнате»:
  выключенный не опрашивает SDK и не публикует своё значение;
- тесты: ряд хоста и подписи, пинг в плитке своего и чужого участника,
  публикация по служебной теме, выключенный пинг не дёргает SDK.
This commit is contained in:
2026-09-20 22:10:17 +03:00
parent 5668d779df
commit 3fe634c9bc
16 changed files with 476 additions and 51 deletions
@@ -31,6 +31,8 @@ export function VoiceOccupantRow({
const { t } = useTranslation();
const permissions = useVoiceModeration(guildId);
const moveTargets = useVoiceChannels(guildId).filter((channel) => channel.id !== channelId);
// Подписка именно на карту пингов: селектор-функция не вызывает перерисовку.
const latencies = useVoiceConnectionStore((state) => state.latencies);
const setParticipantMuted = useVoiceConnectionStore((state) => state.setParticipantMuted);
const setParticipantDeafened = useVoiceConnectionStore((state) => state.setParticipantDeafened);
const moveParticipant = useVoiceConnectionStore((state) => state.moveParticipant);
@@ -87,7 +89,7 @@ export function VoiceOccupantRow({
camera={occupant.camera}
screen={occupant.screen}
/>
<VoiceQualityDot quality={occupant.quality} />
<VoiceQualityDot quality={occupant.quality} latencyMs={latencies[occupant.userId]} />
</button>
<VoiceOccupantMenu