#include "dashboard.h" #include "buffer.h" #include "monitor.h" #include "connection.h" #include "../cJSON.h" #include #include #include #include static const char *DASHBOARD_HTML = "\n" "\n" "\n" " Reverse Proxy Monitor\n" " \n" " \n" "\n" "\n" "
\n" "
\n" "
0
\n" "
Connections
\n" "
\n" "
\n" "
0
\n" "
Memory
\n" "
\n" "
\n" "
0
\n" "
CPU %
\n" "
\n" "
\n" "
0.00
\n" "
Load 1m
\n" "
\n" "
\n" "
0.00
\n" "
Load 5m
\n" "
\n" "
\n" "
0.00
\n" "
Load 15m
\n" "
\n" "
\n" "\n" "
\n" "
CPU Usage
\n" " \n" "
\n" "\n" "
\n" "
Memory Usage
\n" " \n" "
\n" "\n" "
\n" "
Network I/O
\n" "
\n" "
RX
\n" "
TX
\n" "
\n" " \n" "
\n" "\n" "
\n" "
Disk I/O
\n" "
\n" "
Read
\n" "
Write
\n" "
\n" " \n" "
\n" "\n" "
\n" "
Load Average
\n" "
\n" "
1 min
\n" "
5 min
\n" "
15 min
\n" "
\n" " \n" "
\n" "\n" "
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Virtual HostHTTP ReqWS ReqTotal ReqAvg Resp (ms)SentReceived
\n" "
\n" "\n" " \n" "\n" "\n"; void dashboard_serve(connection_t *conn) { if (!conn) return; size_t content_len = strlen(DASHBOARD_HTML); char header[512]; int len = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=utf-8\r\n" "Content-Length: %zu\r\n" "Connection: %s\r\n" "Cache-Control: no-cache\r\n" "\r\n", content_len, conn->request.keep_alive ? "keep-alive" : "close"); if (buffer_ensure_capacity(&conn->write_buf, conn->write_buf.tail + len + content_len) < 0) { connection_send_error_response(conn, 500, "Internal Server Error", "Memory allocation failed"); return; } memcpy(conn->write_buf.data + conn->write_buf.tail, header, len); conn->write_buf.tail += len; memcpy(conn->write_buf.data + conn->write_buf.tail, DASHBOARD_HTML, content_len); conn->write_buf.tail += content_len; struct epoll_event event = { .data.fd = conn->fd, .events = EPOLLIN | EPOLLOUT }; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &event); } static cJSON* format_history(history_deque_t *dq, int window_seconds) { cJSON *arr = cJSON_CreateArray(); if (!arr || !dq || !dq->points || dq->count == 0) return arr; double current_time = time(NULL); int start_index = (dq->head - dq->count + dq->capacity) % dq->capacity; for (int i = 0; i < dq->count; ++i) { int current_index = (start_index + i) % dq->capacity; history_point_t *p = &dq->points[current_index]; if ((current_time - p->time) <= window_seconds) { cJSON *pt = cJSON_CreateObject(); if (pt) { cJSON_AddNumberToObject(pt, "x", (long)((p->time - current_time) * 1000)); cJSON_AddNumberToObject(pt, "y", p->value); cJSON_AddItemToArray(arr, pt); } } } return arr; } static cJSON* format_network_history(network_history_deque_t *dq, int window_seconds, const char *key) { cJSON *arr = cJSON_CreateArray(); if (!arr || !dq || !dq->points || !key || dq->count == 0) return arr; double current_time = time(NULL); int start_index = (dq->head - dq->count + dq->capacity) % dq->capacity; for (int i = 0; i < dq->count; ++i) { int current_index = (start_index + i) % dq->capacity; network_history_point_t *p = &dq->points[current_index]; if ((current_time - p->time) <= window_seconds) { cJSON *pt = cJSON_CreateObject(); if (pt) { cJSON_AddNumberToObject(pt, "x", (long)((p->time - current_time) * 1000)); cJSON_AddNumberToObject(pt, "y", strcmp(key, "rx_kbps") == 0 ? p->rx_kbps : p->tx_kbps); cJSON_AddItemToArray(arr, pt); } } } return arr; } static cJSON* format_disk_history(disk_history_deque_t *dq, int window_seconds, const char *key) { cJSON *arr = cJSON_CreateArray(); if (!arr || !dq || !dq->points || !key || dq->count == 0) return arr; double current_time = time(NULL); int start_index = (dq->head - dq->count + dq->capacity) % dq->capacity; for (int i = 0; i < dq->count; ++i) { int current_index = (start_index + i) % dq->capacity; disk_history_point_t *p = &dq->points[current_index]; if ((current_time - p->time) <= window_seconds) { cJSON *pt = cJSON_CreateObject(); if (pt) { cJSON_AddNumberToObject(pt, "x", (long)((p->time - current_time) * 1000)); cJSON_AddNumberToObject(pt, "y", strcmp(key, "read_mbps") == 0 ? p->read_mbps : p->write_mbps); cJSON_AddItemToArray(arr, pt); } } } return arr; } void dashboard_serve_stats_api(connection_t *conn) { if (!conn) return; cJSON *root = cJSON_CreateObject(); if (!root) { connection_send_error_response(conn, 500, "Internal Server Error", "JSON creation failed"); return; } cJSON *current = cJSON_CreateObject(); if (!current) { cJSON_Delete(root); connection_send_error_response(conn, 500, "Internal Server Error", "JSON creation failed"); return; } cJSON_AddItemToObject(root, "current", current); char buffer[64]; double last_cpu = 0, last_mem = 0; double load1 = 0, load5 = 0, load15 = 0; if (monitor.cpu_history.count > 0) { int last_idx = (monitor.cpu_history.head - 1 + monitor.cpu_history.capacity) % monitor.cpu_history.capacity; last_cpu = monitor.cpu_history.points[last_idx].value; } if (monitor.memory_history.count > 0) { int last_idx = (monitor.memory_history.head - 1 + monitor.memory_history.capacity) % monitor.memory_history.capacity; last_mem = monitor.memory_history.points[last_idx].value; } if (monitor.load1_history.count > 0) { int idx = (monitor.load1_history.head - 1 + monitor.load1_history.capacity) % monitor.load1_history.capacity; load1 = monitor.load1_history.points[idx].value; } if (monitor.load5_history.count > 0) { int idx = (monitor.load5_history.head - 1 + monitor.load5_history.capacity) % monitor.load5_history.capacity; load5 = monitor.load5_history.points[idx].value; } if (monitor.load15_history.count > 0) { int idx = (monitor.load15_history.head - 1 + monitor.load15_history.capacity) % monitor.load15_history.capacity; load15 = monitor.load15_history.points[idx].value; } snprintf(buffer, sizeof(buffer), "%.2f", last_cpu); cJSON_AddStringToObject(current, "cpu_percent", buffer); snprintf(buffer, sizeof(buffer), "%.2f", last_mem); cJSON_AddStringToObject(current, "memory_gb", buffer); cJSON_AddNumberToObject(current, "active_connections", monitor.active_connections); cJSON_AddNumberToObject(current, "load_1m", load1); cJSON_AddNumberToObject(current, "load_5m", load5); cJSON_AddNumberToObject(current, "load_15m", load15); cJSON_AddItemToObject(root, "cpu_history", format_history(&monitor.cpu_history, HISTORY_SECONDS)); cJSON_AddItemToObject(root, "memory_history", format_history(&monitor.memory_history, HISTORY_SECONDS)); cJSON_AddItemToObject(root, "network_rx_history", format_network_history(&monitor.network_history, HISTORY_SECONDS, "rx_kbps")); cJSON_AddItemToObject(root, "network_tx_history", format_network_history(&monitor.network_history, HISTORY_SECONDS, "tx_kbps")); cJSON_AddItemToObject(root, "disk_read_history", format_disk_history(&monitor.disk_history, HISTORY_SECONDS, "read_mbps")); cJSON_AddItemToObject(root, "disk_write_history", format_disk_history(&monitor.disk_history, HISTORY_SECONDS, "write_mbps")); cJSON_AddItemToObject(root, "throughput_history", format_history(&monitor.throughput_history, HISTORY_SECONDS)); cJSON_AddItemToObject(root, "load1_history", format_history(&monitor.load1_history, HISTORY_SECONDS)); cJSON_AddItemToObject(root, "load5_history", format_history(&monitor.load5_history, HISTORY_SECONDS)); cJSON_AddItemToObject(root, "load15_history", format_history(&monitor.load15_history, HISTORY_SECONDS)); cJSON *processes = cJSON_CreateArray(); if (processes) { cJSON_AddItemToObject(root, "processes", processes); for (vhost_stats_t *s = monitor.vhost_stats_head; s; s = s->next) { cJSON *p = cJSON_CreateObject(); if (p) { cJSON_AddStringToObject(p, "name", s->vhost_name); cJSON_AddNumberToObject(p, "http_requests", s->http_requests); cJSON_AddNumberToObject(p, "websocket_requests", s->websocket_requests); cJSON_AddNumberToObject(p, "total_requests", s->total_requests); cJSON_AddNumberToObject(p, "avg_request_time_ms", s->avg_request_time_ms); cJSON_AddNumberToObject(p, "bytes_sent", s->bytes_sent); cJSON_AddNumberToObject(p, "bytes_recv", s->bytes_recv); cJSON_AddItemToObject(p, "throughput_history", format_history(&s->throughput_history, 60)); cJSON_AddItemToArray(processes, p); } } } char *json_string = cJSON_PrintUnformatted(root); if (!json_string) { cJSON_Delete(root); connection_send_error_response(conn, 500, "Internal Server Error", "JSON serialization failed"); return; } char header[512]; int hlen = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=utf-8\r\n" "Content-Length: %zu\r\n" "Connection: %s\r\n" "Cache-Control: no-cache\r\n" "\r\n", strlen(json_string), conn->request.keep_alive ? "keep-alive" : "close"); if (buffer_ensure_capacity(&conn->write_buf, conn->write_buf.tail + hlen + strlen(json_string)) < 0) { free(json_string); cJSON_Delete(root); connection_send_error_response(conn, 500, "Internal Server Error", "Memory allocation failed"); return; } memcpy(conn->write_buf.data + conn->write_buf.tail, header, hlen); conn->write_buf.tail += hlen; memcpy(conn->write_buf.data + conn->write_buf.tail, json_string, strlen(json_string)); conn->write_buf.tail += strlen(json_string); struct epoll_event event = { .data.fd = conn->fd, .events = EPOLLIN | EPOLLOUT }; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &event); cJSON_Delete(root); free(json_string); }