diff --git a/tools/etcpmon/etcpmon_client.c b/tools/etcpmon/etcpmon_client.c index d3e5e7d6..5060dfd5 100644 --- a/tools/etcpmon/etcpmon_client.c +++ b/tools/etcpmon/etcpmon_client.c @@ -927,6 +927,7 @@ int etcpmon_client_process(struct etcpmon_client* client) { } handle_response_with_callback(client, hdr->type, payload, payload_size); + if (!client->connected) return -1; processed++; /* Удаляем обработанное сообщение */ @@ -934,7 +935,6 @@ int etcpmon_client_process(struct etcpmon_client* client) { memmove(client->recv_buffer, client->recv_buffer + total_size, client->recv_len - total_size); } - if (!client->connected) return -1; client->recv_len -= total_size; } diff --git a/tools/etcpmon/etcpmon_doc.md b/tools/etcpmon/etcpmon_doc.md index 4e22cb89..fac0c8ab 100644 --- a/tools/etcpmon/etcpmon_doc.md +++ b/tools/etcpmon/etcpmon_doc.md @@ -4,6 +4,12 @@ Windows GUI монитор для ETCP соединений uTun в реальном времени (100ms refresh). Подключается по TCP к control_server внутри uTun и отображает метрики соединений, линков, TUN-интерфейса, роутера и дебаг-уровней. Позволяет менять debug levels на лету через GUI. +Connections и локальные сокеты опрашиваются раз в секунду, даже при пустом списке соединений. +Выбор сохраняется по ID узла при изменении порядка и имён. При удалении выбранного соединения +выбирается первое доступное; пустой список или отключение очищает метрики и график. +Одновременно ожидается не более одного ответа на автоматический запрос метрик. Таймаут ответа +на запрос с seq_id — 5 секунд; причина отключения записывается в `etcpmon.log`. + ## 2. Как пользоваться ### Типовой сценарий diff --git a/tools/etcpmon/etcpmon_gui.c b/tools/etcpmon/etcpmon_gui.c index a8f15a88..fbe53e7d 100644 --- a/tools/etcpmon/etcpmon_gui.c +++ b/tools/etcpmon/etcpmon_gui.c @@ -22,7 +22,8 @@ #define WINDOW_WIDTH 1000 #define WINDOW_HEIGHT 1400 #define WINDOW_CLIENT_W 1250 -#define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */ +#define UPDATE_INTERVAL 10 +#define LIST_UPDATE_INTERVAL 1000 /* Global app pointer for callbacks */ static struct etcpmon_app* g_app = NULL; /* Forward declarations */ @@ -1121,8 +1122,8 @@ static void OnConnect(struct etcpmon_app* app) { if (etcpmon_client_connect(app->client, addr, port) == 0) { app->isConnected = 1; + app->need_initial_request = 1; UpdateUIState(app); - etcpmon_gui_set_status(app, "Connected"); } else { etcpmon_gui_set_status(app, "Connection failed"); } @@ -1131,11 +1132,7 @@ static void OnDisconnect(struct etcpmon_app* app) { if (!app || !app->client) return; etcpmon_client_disconnect(app->client); - app->isConnected = 0; - app->need_initial_request = 1; - app->last_selected_peer_id = 0; - UpdateUIState(app); - etcpmon_gui_set_status(app, "Disconnected"); + on_disconnected(app); } static void OnConnectionSelect(struct etcpmon_app* app) { @@ -1144,14 +1141,19 @@ static void OnConnectionSelect(struct etcpmon_app* app) { int idx = (int)SendMessage(app->hListConnections, LB_GETCURSEL, 0, 0); if (idx == LB_ERR) return; - uint64_t peer_id = (uint64_t)SendMessage(app->hListConnections, LB_GETITEMDATA, idx, 0); + LRESULT list_idx = SendMessage(app->hListConnections, LB_GETITEMDATA, idx, 0); + if (list_idx < 0 || list_idx >= app->client->conn_count) return; + uint64_t peer_id = app->client->conn_list[list_idx].peer_node_id; if (peer_id == 0) return; /* Только если действительно изменилось — иначе спамим сервер */ if (peer_id != app->last_selected_peer_id) { + if (etcpmon_client_select_connection(app->client, peer_id) != 0) { + OnDisconnect(app); + etcpmon_gui_set_status(app, "Failed to select connection"); + return; + } app->last_selected_peer_id = peer_id; - - etcpmon_client_select_connection(app->client, peer_id); etcpmon_gui_clear_metrics(app); /* очищаем старые цифры */ etcpmon_gui_set_status(app, "Connection selected"); @@ -1264,9 +1266,7 @@ static void RefreshDebugUI(struct etcpmon_app* app) { static void OnTimer(struct etcpmon_app* app) { if (!app || !app->client) return; - -// fprintf(app->client->log_file, "Timer... \n"); -// fflush(app->client->log_file); + if (!app->client->connected) return; int processed = etcpmon_client_process(app->client); if (processed < 0) { @@ -1274,10 +1274,17 @@ static void OnTimer(struct etcpmon_app* app) { return; } - if (app->need_initial_request && app->isConnected) { - etcpmon_client_request_list(app->client); - etcpmon_client_request_socket_list(app->client); - etcpmon_client_request_debug_config(app->client); + if (!app->client->fully_connected) return; + + DWORD now = GetTickCount(); + if (app->need_initial_request || now - app->last_poll_time >= LIST_UPDATE_INTERVAL) { + if (etcpmon_client_request_list(app->client) != 0 || etcpmon_client_request_socket_list(app->client) != 0 || + (app->need_initial_request && etcpmon_client_request_debug_config(app->client) != 0)) { + OnDisconnect(app); + etcpmon_gui_set_status(app, "Failed to request monitor data"); + return; + } + app->last_poll_time = now; app->need_initial_request = 0; } @@ -1324,6 +1331,12 @@ void etcpmon_gui_update_graph(struct etcpmon_app* app, struct etcpmon_rsp_metric static void on_disconnected(void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; app->isConnected = 0; + app->need_initial_request = 1; + app->last_selected_peer_id = 0; + app->last_poll_time = 0; + SendMessage(app->hListConnections, LB_RESETCONTENT, 0, 0); + SendMessage(app->hListSockets, LB_RESETCONTENT, 0, 0); + etcpmon_gui_clear_metrics(app); UpdateUIState(app); etcpmon_gui_set_status(app, "Disconnected"); } @@ -1333,17 +1346,16 @@ static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* us etcpmon_gui_update_conn_list(app, list, count); - /* Автоматически выбираем первое соединение (только один раз) */ - if (count > 0 && app->last_selected_peer_id == 0) { - uint64_t first_id = list[0].peer_node_id; - app->last_selected_peer_id = first_id; - - etcpmon_client_select_connection(app->client, first_id); - - /* Выделяем первую строку в списке */ - SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); - - etcpmon_gui_set_status(app, "Auto-selected first connection"); + if (count > 0) { + OnConnectionSelect(app); + } else if (app->last_selected_peer_id != 0) { + if (etcpmon_client_select_connection(app->client, 0) != 0) { + OnDisconnect(app); + return; + } + app->last_selected_peer_id = 0; + etcpmon_gui_clear_metrics(app); + etcpmon_gui_set_status(app, "No connections"); } } @@ -1358,7 +1370,6 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics, struct etcpmon_app* app = (struct etcpmon_app*)user_data; if (app) { etcpmon_gui_update_metrics(app, metrics, links, links_count); - etcpmon_client_add_to_history(app->client, metrics); if (!app->graph_cursor_active) { struct metrics_history* h = etcpmon_client_get_history(app->client); if (h && h->count > 0) { @@ -1522,7 +1533,24 @@ void etcpmon_gui_update_conn_list(struct etcpmon_app* app, uint8_t count) { if (!app || !app->hListConnections) return; - /* Clear existing items */ + int selected = count ? 0 : -1; + int changed = SendMessage(app->hListConnections, LB_GETCOUNT, 0, 0) != count; + for (uint8_t i = 0; i < count; i++) { + if (list[i].peer_node_id == app->last_selected_peer_id) selected = i; + char display[64], previous[64]; + snprintf(display, sizeof(display), "%s (%016llX)", list[i].name, (unsigned long long)list[i].peer_node_id); + if (!changed && (SendMessage(app->hListConnections, LB_GETTEXTLEN, i, 0) >= (LRESULT)sizeof(previous) || + SendMessageA(app->hListConnections, LB_GETTEXT, i, (LPARAM)previous) == LB_ERR || strcmp(display, previous) != 0)) { + changed = 1; + } + } + if (!changed) { + SendMessage(app->hListConnections, LB_SETCURSEL, selected, 0); + return; + } + + int top = (int)SendMessage(app->hListConnections, LB_GETTOPINDEX, 0, 0); + SendMessage(app->hListConnections, WM_SETREDRAW, FALSE, 0); SendMessage(app->hListConnections, LB_RESETCONTENT, 0, 0); /* Add new items */ @@ -1532,10 +1560,22 @@ void etcpmon_gui_update_conn_list(struct etcpmon_app* app, list[i].name, (unsigned long long)list[i].peer_node_id); int idx = (int)SendMessageA(app->hListConnections, LB_ADDSTRING, 0, (LPARAM)display); - if (idx != LB_ERR) { - SendMessage(app->hListConnections, LB_SETITEMDATA, idx, (LPARAM)list[i].peer_node_id); + if (idx != LB_ERR && idx != LB_ERRSPACE) { + /* Индекс сохраняет полный 64-битный ID и в 32-битной Windows. */ + SendMessage(app->hListConnections, LB_SETITEMDATA, idx, i); + } else if (app->client && app->client->log_file) { + fprintf(app->client->log_file, "[ERROR] Failed to add connection row: index=%u result=%d\n", i, idx); } } + SendMessage(app->hListConnections, LB_SETCURSEL, selected, 0); + if (top >= 0 && top < count) SendMessage(app->hListConnections, LB_SETTOPINDEX, top, 0); + SendMessage(app->hListConnections, WM_SETREDRAW, TRUE, 0); + InvalidateRect(app->hListConnections, NULL, TRUE); + if (app->client && app->client->log_file) { + fprintf(app->client->log_file, "[LOG] Connections updated: count=%u selected_row=%d previous_peer=%016llX\n", + count, selected, (unsigned long long)app->last_selected_peer_id); + fflush(app->client->log_file); + } } static const char* config_type_to_string(uint8_t config_type) { @@ -1565,7 +1605,7 @@ static const char* nat_type_to_string(uint8_t nat_type) { void etcpmon_gui_update_socket_list(struct etcpmon_app* app, struct etcpmon_socket_info* list, uint8_t count) { - if (!app || !app->hListSockets || !list || count == 0 || count > 32) return; + if (!app || !app->hListSockets) return; SendMessage(app->hListSockets, LB_RESETCONTENT, 0, 0); @@ -1917,4 +1957,15 @@ void etcpmon_gui_clear_metrics(struct etcpmon_app* app) { if (app->hListLinks) { SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); } + if (app->client) { + memset(&app->client->history, 0, sizeof(app->client->history)); + free(app->client->last_metrics); + app->client->last_metrics = NULL; + free(app->client->last_links); + app->client->last_links = NULL; + app->client->last_links_count = 0; + } + app->graph_cursor_active = 0; + for (int i = 0; i < GRAPH_METRICS_COUNT; i++) SetWindowTextA(app->hChannelValue[i], ""); + if (app->hGraphWnd) InvalidateRect(app->hGraphWnd, NULL, TRUE); }