diff --git a/FAVICON_OPTIONS.md b/FAVICON_OPTIONS.md deleted file mode 100644 index bca8075..0000000 --- a/FAVICON_OPTIONS.md +++ /dev/null @@ -1,46 +0,0 @@ -# Варианты favicon для NATS UI - -В проекте две группы иконок (32×32 PNG): дизайн «два облачка + линия» в разных цветах и альтернативные дизайны. - -## Цветовые варианты (два облачка и изогнутая линия) - -Один и тот же дизайн — разные фоны и акценты. - -| Файл | Цвета | -|------|--------| -| `favicon_variant0_original.png` | Исходный: приглушённый синий фон, белые облачка и линия | -| `favicon_variant1_purple.png` | Тёмно-фиолетовый фон, белые формы | -| `favicon_variant2_navy_cyan.png` | Тёмно-синий фон, голубые (cyan) формы | -| `favicon_variant3_teal.png` | Бирюзовый фон, белые формы | -| `favicon_variant4_black.png` | Чёрный фон, белые формы | -| `favicon_variant5_coral.png` | Кораллово-оранжевый фон, белые формы | -| `favicon_variant6_violet.png` | Фиолетовый фон, белые формы | -| `favicon_variant7_green.png` | Тёмно-зелёный фон, белые формы | - -## Другие дизайны - -| Файл | Описание | -|------|----------| -| `favicon_option1.png` | Молния/стрелка на фиолетовом | -| `favicon_option2.png` | Синий круг, три точки | -| `favicon_option3.png` | Стилизованная буква N | -| `favicon_option4.png` | Два перекрывающихся облачка/конверта | -| `favicon_option5.png` | Узлы и связи | - -## Как выбрать иконку - -1. Откройте файлы в проводнике или в браузере (если раздаёте статику). -2. Выберите понравившийся вариант. -3. Замените текущий favicon, например: - ```bash - cp favicon_variant3_teal.png favicon.ico - # или - cp favicon_option4.png favicon.ico - ``` -4. Пересоберите приложение: - ```bash - go build -o nats-ui . - ``` - Или пересоберите Docker-образ. - -Браузеры принимают PNG в качестве favicon (файл может называться `favicon.ico`). diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index a42255e..0000000 Binary files a/favicon.ico and /dev/null differ diff --git a/index.html b/index.html index de38476..f15343c 100644 --- a/index.html +++ b/index.html @@ -39,13 +39,30 @@ margin-bottom: 10px; } + .header-info { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + flex-wrap: wrap; + margin-top: 12px; + font-size: 0.95em; + } + .header .status { display: inline-block; padding: 8px 16px; background: rgba(255, 255, 255, 0.2); border-radius: 20px; font-size: 0.9em; - margin-top: 10px; + } + + .header-sep { + opacity: 0.7; + } + + .header-info strong { + font-weight: 600; } .status.connected { @@ -326,7 +343,11 @@

🚀 NATS Queue Visualizer

-
Отключено
+
+ Отключено + + Подписки: +
@@ -565,6 +586,19 @@ updateMessages(); }); + function loadSubscribed() { + fetch('/api/subscribed') + .then(res => res.json()) + .then(data => { + const list = data.subscribed || []; + const el = document.getElementById('subscribedList'); + el.textContent = list.length ? list.join(', ') : '—'; + }) + .catch(() => { + document.getElementById('subscribedList').textContent = '—'; + }); + } + function loadConnections() { fetch('/api/connections') .then(res => res.json()) @@ -613,6 +647,7 @@ // Инициализация - только WebSocket, все сообщения придут автоматически connectWebSocket(); + loadSubscribed(); loadConnections(); setInterval(loadConnections, 5000); diff --git a/main.go b/main.go index dcfb4c1..7296afa 100644 --- a/main.go +++ b/main.go @@ -146,10 +146,11 @@ type Config struct { type connzResponse struct { Connections []connInfo `json:"connections"` + Conns []connInfo `json:"conns"` } type connInfo struct { - CID int `json:"cid"` + CID uint64 `json:"cid"` IP string `json:"ip"` Port int `json:"port"` Name string `json:"name"` @@ -170,6 +171,7 @@ func connectionsHandler(w http.ResponseWriter, _ *http.Request, cfg *Config) { return } url := strings.TrimSuffix(cfg.NatsMonitorURL, "/") + "/connz" + log.Printf("Fetching NATS connections from %s", url) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -181,7 +183,7 @@ func connectionsHandler(w http.ResponseWriter, _ *http.Request, cfg *Config) { client := &http.Client{Timeout: 5 * time.Second} resp, err := client.Do(req) if err != nil { - log.Printf("Failed to fetch NATS connz: %v", err) + log.Printf("Failed to fetch NATS connz from %s: %v", url, err) if err := json.NewEncoder(w).Encode(connzResponse{Connections: []connInfo{}}); err != nil { log.Printf("Failed to encode connections: %v", err) } @@ -194,7 +196,7 @@ func connectionsHandler(w http.ResponseWriter, _ *http.Request, cfg *Config) { return } if resp.StatusCode != http.StatusOK { - log.Printf("NATS connz returned status %d", resp.StatusCode) + log.Printf("NATS connz %s returned status %d, body: %s", url, resp.StatusCode, truncate(string(body), 300)) if err := json.NewEncoder(w).Encode(connzResponse{Connections: []connInfo{}}); err != nil { log.Printf("Failed to encode connections: %v", err) } @@ -202,17 +204,24 @@ func connectionsHandler(w http.ResponseWriter, _ *http.Request, cfg *Config) { } var connz connzResponse if err := json.Unmarshal(body, &connz); err != nil { - log.Printf("Failed to parse connz: %v", err) - if err := json.NewEncoder(w).Encode(connzResponse{Connections: []connInfo{}}); err != nil { - log.Printf("Failed to encode connections: %v", err) - } - return + log.Printf("Failed to parse NATS connz from %s: %v, body sample: %s", url, err, truncate(string(body), 200)) } - if err := json.NewEncoder(w).Encode(connz); err != nil { + if len(connz.Connections) == 0 && len(connz.Conns) > 0 { + connz.Connections = connz.Conns + } + out := connzResponse{Connections: connz.Connections} + if err := json.NewEncoder(w).Encode(out); err != nil { log.Printf("Failed to encode connections: %v", err) } } +func truncate(s string, max int) string { + if len(s) <= max { + return s + } + return s[:max] + "..." +} + func extractProducerFromPayload(data []byte) string { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { @@ -264,7 +273,6 @@ func main() { } log.Fatalf("Failed to load config: %v", err) } - log.Printf("Loaded configuration from %s", configFile) store := NewMessageStore(config.MaxMessages) hub := NewHub() go hub.Run() @@ -272,17 +280,14 @@ func main() { opts = append(opts, nats.Name("nats-ui")) if config.Token != "" { opts = append(opts, nats.Token(config.Token)) - log.Printf("Using token authentication") } else if config.Username != "" || config.Password != "" { opts = append(opts, nats.UserInfo(config.Username, config.Password)) - log.Printf("Using username/password authentication") } nc, err := nats.Connect(config.NatsURL, opts...) if err != nil { log.Fatalf("Failed to connect to NATS: %v", err) } defer nc.Close() - log.Printf("Connected to NATS at %s", config.NatsURL) subjectsList := parseSubjects(config.Subjects) for _, subject := range subjectsList { subj := subject @@ -299,12 +304,10 @@ func main() { } store.Add(message) hub.broadcast <- message - log.Printf("Received message on %s: %d bytes", msg.Subject, len(msg.Data)) }) if err != nil { log.Fatalf("Failed to subscribe to %s: %v", subj, err) } - log.Printf("Subscribed to: %s", subj) defer func() { if err := sub.Unsubscribe(); err != nil { log.Printf("Failed to unsubscribe from %s: %v", subj, err) @@ -347,6 +350,13 @@ func main() { log.Printf("Failed to encode subjects: %v", err) } }) + http.HandleFunc("/api/subscribed", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + list := parseSubjects(config.Subjects) + if err := json.NewEncoder(w).Encode(map[string]interface{}{"subscribed": list}); err != nil { + log.Printf("Failed to encode subscribed: %v", err) + } + }) http.HandleFunc("/api/connections", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") connectionsHandler(w, r, config) @@ -358,18 +368,13 @@ func main() { return } hub.register <- conn - - // Отправляем все существующие сообщения одним массивом при подключении go func() { messages := store.GetAll() - // Отправляем массив всех сообщений if err := conn.WriteJSON(messages); err != nil { log.Printf("Error sending initial messages: %v", err) return } }() - - // Читаем сообщения от клиента (для keep-alive) go func() { for { _, _, err := conn.ReadMessage() @@ -380,8 +385,7 @@ func main() { } }() }) - log.Printf("Starting HTTP server on port %s", config.Port) - log.Printf("Open http://localhost:%s in your browser", config.Port) + log.Printf("NATS UI: http://0.0.0.0:%s", config.Port) addr := ":" + config.Port log.Fatal(http.ListenAndServe(addr, nil)) }