feat: balancer dashboard metrics + fix WebSocket Hijacker
This commit is contained in:
@@ -184,6 +184,22 @@ export async function deleteUser(id: string) {
|
||||
}
|
||||
|
||||
// --- WebSocket ---
|
||||
export interface BalancerInfo {
|
||||
esl_connected: boolean;
|
||||
esl_uptime_sec: number;
|
||||
esl_reconnects: number;
|
||||
nats_connected: boolean;
|
||||
gateways_total: number;
|
||||
gateways_up: number;
|
||||
gateways_down: number;
|
||||
fs_active_calls: number;
|
||||
fs_max_sessions: number;
|
||||
fs_uptime: string;
|
||||
route_total: number;
|
||||
route_fallbacks: number;
|
||||
uptime_sec: number;
|
||||
}
|
||||
|
||||
export function connectWebSocket(onMessage: (data: any) => void): WebSocket {
|
||||
const token = getToken();
|
||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import {
|
||||
getNodes, connectWebSocket, isAuthenticated,
|
||||
type NodeInfo
|
||||
type NodeInfo, type BalancerInfo
|
||||
} from '$lib/api';
|
||||
import { Activity, Phone, Zap, AlertTriangle, Wifi, Server } from 'lucide-svelte';
|
||||
import { Activity, Phone, Zap, AlertTriangle, Wifi, Server, Plug, Globe, ArrowLeftRight, Network } from 'lucide-svelte';
|
||||
|
||||
let nodes: NodeInfo[] = [];
|
||||
let balancer: BalancerInfo | null = null;
|
||||
let ws: WebSocket | null = null;
|
||||
let error: string | null = null;
|
||||
|
||||
@@ -21,6 +22,8 @@
|
||||
ws = connectWebSocket((msg) => {
|
||||
if (msg.type === 'nodes_update') {
|
||||
nodes = msg.payload;
|
||||
} else if (msg.type === 'balancer_update') {
|
||||
balancer = msg.payload;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -67,10 +70,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function dotColor(ok: boolean, degrade?: boolean): string {
|
||||
if (ok) return 'bg-green-500';
|
||||
if (degrade) return 'bg-yellow-500';
|
||||
return 'bg-red-500';
|
||||
}
|
||||
|
||||
function fmtUptime(sec: number): string {
|
||||
if (sec < 60) return `${sec}с`;
|
||||
if (sec < 3600) return `${Math.floor(sec / 60)}м`;
|
||||
if (sec < 86400) return `${Math.floor(sec / 3600)}ч`;
|
||||
return `${Math.floor(sec / 86400)}д`;
|
||||
}
|
||||
|
||||
function fmtNum(n: number): string {
|
||||
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
|
||||
return String(n);
|
||||
}
|
||||
|
||||
$: healthyCount = nodes.filter(n => n.score >= 0 && !n.disabled && !n.is_stale).length;
|
||||
$: totalCalls = nodes.reduce((s, n) => s + n.active_calls, 0);
|
||||
$: avgScore = nodes.length > 0 ? nodes.reduce((s, n) => s + n.score, 0) / nodes.length : 0;
|
||||
$: unhealthyList = nodes.filter(n => n.score < 0 || n.disabled || n.is_stale);
|
||||
$: fallbackPct = balancer && balancer.route_total > 0
|
||||
? (balancer.route_fallbacks / balancer.route_total * 100).toFixed(1)
|
||||
: '0';
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
@@ -79,6 +103,51 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Balancer Status Bar -->
|
||||
{#if balancer}
|
||||
<div class="flex flex-wrap items-center gap-1 text-xs text-muted-foreground mb-4 px-3 py-2 border rounded-md bg-muted/30">
|
||||
<span class="flex items-center gap-1 mr-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full {dotColor(balancer.esl_connected)}"></span>
|
||||
<Plug class="h-3 w-3" />
|
||||
ESL {balancer.esl_connected ? 'Online' : 'Offline'}
|
||||
{#if balancer.esl_uptime_sec > 0}{fmtUptime(balancer.esl_uptime_sec)}{/if}
|
||||
</span>
|
||||
<span class="opacity-30">|</span>
|
||||
<span class="flex items-center gap-1 mr-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full {dotColor(balancer.nats_connected)}"></span>
|
||||
<Network class="h-3 w-3" />
|
||||
NATS {balancer.nats_connected ? 'Online' : 'Offline'}
|
||||
</span>
|
||||
<span class="opacity-30">|</span>
|
||||
<span class="flex items-center gap-1 mr-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full {balancer.gateways_down > 0 ? 'bg-red-500' : 'bg-green-500'}"></span>
|
||||
<Globe class="h-3 w-3" />
|
||||
GW {balancer.gateways_up}/{balancer.gateways_total}
|
||||
{#if balancer.gateways_down > 0}
|
||||
<span class="text-red-500">({balancer.gateways_down} down)</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="opacity-30">|</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<ArrowLeftRight class="h-3 w-3" />
|
||||
R {fmtNum(balancer.route_total)}
|
||||
{#if balancer.route_fallbacks > 0}
|
||||
<span class="text-orange-500">({fallbackPct}% fb)</span>
|
||||
{/if}
|
||||
</span>
|
||||
{#if balancer.esl_connected && balancer.fs_max_sessions > 0}
|
||||
<span class="opacity-30">|</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<Phone class="h-3 w-3" />
|
||||
FS {balancer.fs_active_calls}·{balancer.fs_max_sessions}
|
||||
{#if balancer.fs_uptime}
|
||||
<span class="opacity-50">{balancer.fs_uptime}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="border rounded-lg p-4 bg-card">
|
||||
|
||||
Reference in New Issue
Block a user