feat: auto-trunk creation, SIP gateway in metrics, gateway WS broadcast

- SIPGateway field in NodeMetric model
- HasNode() engine method
- OnNewNode callback in NATS subscriber
- Auto-create balance trunk from new node metric
- BroadcastGatewayEvent on WS hub
- Gateway status indicators in trunks UI (green/red/gray)
- SPA fallback for frontend routing
This commit is contained in:
Maksim Totmin
2026-06-25 16:56:49 +07:00
parent 533a1fba49
commit f25f8e4d01
6 changed files with 141 additions and 10 deletions
+31 -4
View File
@@ -1,14 +1,18 @@
<script lang="ts">
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { isAuthenticated, getTrunks, createTrunk, updateTrunk, deleteTrunk, type Trunk } from '$lib/api';
import { onMount, onDestroy } from 'svelte';
import { isAuthenticated, getTrunks, createTrunk, updateTrunk, deleteTrunk, connectWebSocket, type Trunk } from '$lib/api';
import { toast } from 'svelte-sonner';
import { Plus, Trash2, Save, X, Cable } from 'lucide-svelte';
let trunks: Trunk[] = [];
let loading = true;
// Gateway статусы с ESLa (up/down/unknown)
let ws: WebSocket | null = null;
let gatewayStatuses: Record<string, string> = {};
// Edit mode
let editing: string | null = null; // trunk ID being edited
let editData: Partial<Trunk> = {};
@@ -24,7 +28,19 @@
if (browser && !isAuthenticated()) { goto('/login'); }
onMount(loadTrunks);
onMount(() => {
loadTrunks();
ws = connectWebSocket((msg) => {
if (msg.type === 'gateway_update') {
const { trunk_id, status } = msg.payload;
gatewayStatuses = { ...gatewayStatuses, [trunk_id]: status };
}
});
});
onDestroy(() => {
ws?.close();
});
async function loadTrunks() {
loading = true;
@@ -102,6 +118,13 @@
}
}
function gatewayIndicator(trunkID: string): string {
const s = gatewayStatuses[trunkID] || 'unknown';
if (s === 'up') return '🟢';
if (s === 'down') return '🔴';
return '⚪';
}
function onFilterChange(e: Event) {
typeFilter = (e.target as HTMLSelectElement).value;
loadTrunks();
@@ -208,7 +231,11 @@
<tr class="border-t hover:bg-secondary/50">
<td class="px-4 py-3 font-medium">{t.name}</td>
<td class="px-4 py-3"><span class="px-2 py-1 rounded text-xs font-medium {typeBadge(t.type)}">{typeLabel(t.type)}</span></td>
<td class="px-4 py-3 font-mono text-xs">{t.gateway}</td>
<td class="px-4 py-3 font-mono text-xs">
{t.gateway}
{#if t.type === 'ingress'}
<span class="ml-2" title="Gateway: {gatewayStatuses[t.id] || 'unknown'}">{gatewayIndicator(t.id)}</span>
{/if}
<td class="px-4 py-3 font-mono text-xs">{t.node_id || '—'}</td>
<td class="px-4 py-3">{t.enabled ? '🟢 Вкл' : '🔴 Выкл'}</td>
<td class="px-4 py-3 text-right">