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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user