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 @@
@@ -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))
}