diff --git a/config.ini.example b/config.ini.example index fc4e148..ecdf345 100644 --- a/config.ini.example +++ b/config.ini.example @@ -1,35 +1,38 @@ -## STETHOSCOPE EXAMPLE CONFIG - -# The name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope") -service_name=My Stethoscope - -# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800) -listen=7800 - -# Path of the webroot server uses to load asset paths like the stylesheet. (default: ) -webroot= +################################ +## STETHOSCOPE EXAMPLE CONFIG ## +################################ +# Name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope") +name=My Stethoscope +# Text to show +description= +# Maximum time in seconds status history is shown on the status page. (default: 86400) +status_retention=86400 +# Maximum time in seconds without change before fetching a new status peroid. (default: 60) +status_refresh=60 # Time in seconds to wait before attempting to reconnect to the endpoint. (default: 5) backoff=5 -# These are example services. -;[service.openssh] -;name=OpenSSH Daemon -;address=tcp://localhost:22 +[web] +# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800) +listen=7800 +# Web path to stylesheet. If this is empty, the daemon will provide a barebones one. +stylesheet= +# Web path to image banner. If this is empty, the page will generate without a banner. +banner= +# Time in seconds between status page recaching. (default: 15) +recache_delay=15 -;[service.nginx] -;name=Nginx -;address=tcp://localhost:80,tcp://localhost:443,process://nginx,pidfile:///run/nginx.pid -;backoff=1 - -;[service.email] -;name=Email -;address=tcp://localhost:25,tcp://localhost:143 - -;[service.tor] -;name=Tor -;address=tcp://127.0.0.1:9050 - -;[service.mumble] -;name=mumble -;address=tcp://localhost:64738,udp://localhost:64738 \ No newline at end of file +## SERVICE EXAMPLE +# Services are always defined in their own section with their name prepended by "service." +;[service.example] +# Display name for the service. This is publicly displayed on the status page. +;name=A Service +# Web path of the icon to use for this service. If empty, an icon will not be used. +;icon= +# Text to display below the service name. +;text= +# Comma-seperated list of endpoints to check the status of for this service +# Endpoints must be in SCHEME://ADDRESS:PORT format or SCHEME://PATH format +# Supported schemes include: tcp, udp, icmp, pid, proc +;endpoints= \ No newline at end of file diff --git a/src/main.c b/src/main.c index d9506bf..f16ef87 100644 --- a/src/main.c +++ b/src/main.c @@ -9,9 +9,25 @@ #define CONFIG_SERVICE_PREFIX "service." +// Global +char* daemon_name = "My Stethoscope"; +char* daemon_description = ""; +uint32_t status_retention = 84600; +uint32_t status_refresh = 60; +uint32_t global_backoff = 15; + +// Web +uint16_t web_port = 7800; +char* web_stylesheet = ""; +char* web_banner = ""; +uint32_t web_recache = 30; + +uint8_t daemon_enabled = 1; +mxml_node_t* cached_index; + static void signal_handler(int signo) { if (signo == SIGINT || signo == SIGKILL) { - running = 0; + daemon_enabled = 0; } else if (signo == SIGUSR1) { printf("USR1 signal detected! Not doing anything for now.\n"); } @@ -19,74 +35,74 @@ static void signal_handler(int signo) { static int config_handler(void* user, const char* section, const char* name, const char* value) { if (strcmp(section, "") == 0) { - if (strcmp(name, "service_name") == 0) { + if (strcmp(name, "name") == 0) { daemon_name = malloc(strlen(value)+1); strcpy(daemon_name, value); - } else if (strcmp(name, "webroot") == 0) { - webroot = malloc(strlen(value)+1); - strcpy(webroot, value); - } else if (strcmp(name, "banner") == 0) { - daemon_banner = malloc(strlen(value)+1); - strcpy(daemon_banner, value); - } else if (strcmp(name, "listen") == 0) { - listen_port = atoi(value); + } else if (strcmp(name, "description") == 0) { + daemon_description = malloc(strlen(value)+1); + strcpy(daemon_description, value); + } else if (strcmp(name, "status_retention") == 0) { + status_retention = atoi(value); + } else if (strcmp(name, "status_refresh") == 0) { + int result = atoi(value); + status_refresh = result > 1 ? result : 1; } else if (strcmp(name, "backoff") == 0) { int result = atoi(value); global_backoff = result > 1 ? result : 1; - } else if (strcmp(name, "web_refresh") == 0) { - int result = atoi(value); - web_refresh = result > 1 ? result : 1; - } else if (strcmp(name, "status_refresh") == 0) { - status_refresh = atoi(value); - } else if (strcmp(name, "retention") == 0) { - retention = atoi(value); } else { - printf("Unknown configuration entry \'%s\'\n", name); + printf("Unknown configuration entry \'%s\' in global\n", name); + } + } else if (strcmp(section, "web") == 0) { + if (strcmp(name, "listen") == 0) { + web_port = atoi(value); + } else if (strcmp(name, "stylesheet") == 0) { + web_stylesheet = malloc(strlen(value)+1); + strcpy(web_stylesheet, value); + } else if (strcmp(name, "banner") == 0) { + web_banner = malloc(strlen(value)+1); + strcpy(web_banner, value); + } else if (strcmp(name, "recache_delay") == 0) { + web_recache = atoi(value); + } else { + printf("Unknown configuration entry \'%s\' in \'%s\'\n", name, section); } } else if (strncmp(CONFIG_SERVICE_PREFIX, section, strlen(CONFIG_SERVICE_PREFIX)) == 0) { const char* id = section+strlen(CONFIG_SERVICE_PREFIX); - Vector* service_vector = (Vector*)user; - struct Service* current_service = NULL; - - Iterator it = vector_begin(service_vector); - Iterator end = vector_end(service_vector); - for (; !iterator_equals(&it, &end); iterator_increment(&it)) { - struct Service* current = *(struct Service**)iterator_get(&it); - if (strcmp(id, current->id) == 0) { - current_service = current; - break; - } - } - if (current_service == NULL) { - current_service = malloc(4096); - current_service->id = malloc(strlen(id)+1); - strcpy(current_service->id, id); - current_service->backoff = 0; - vector_push_back(service_vector, ¤t_service); + struct Service* service = get_service_of_id_or_null(id); + if (service == NULL) { + service = malloc(sizeof(struct Service)); + service->id = malloc(strlen(id)+1); + strcpy(service->id, id); + service->name = ""; + service->text = ""; + service->icon = ""; + service->backoff = global_backoff; + vector_push_back(&services, &service); } if (strcmp(name, "name") == 0) { - current_service->name = malloc(strlen(value)+1); - strcpy(current_service->name, value); + service->name = malloc(strlen(value)+1); + strcpy(service->name, value); } else if (strcmp(name, "text") == 0) { - current_service->text = malloc(strlen(value)+1); - strcpy(current_service->text, value); + service->text = malloc(strlen(value)+1); + strcpy(service->text, value); } else if (strcmp(name, "icon") == 0) { - current_service->icon = malloc(strlen(value)+1); - strcpy(current_service->icon, value); - } else if (strcmp(name, "address") == 0) { + service->icon = malloc(strlen(value)+1); + strcpy(service->icon, value); + } else if (strcmp(name, "endpoints") == 0) { char* endpoints = malloc(strlen(value)); strncpy(endpoints, value, strlen(value)); char* token = strtok(endpoints, ","); - vector_setup(¤t_service->endpoints, 255, sizeof(struct Endpoint*)); + vector_setup(&service->endpoints, 255, sizeof(struct Endpoint*)); while (token != NULL) { - add_endpoint(current_service, token); + add_endpoint(service, token); fflush(stdout); token = strtok(NULL, ","); } + vector_shrink_to_fit(&service->endpoints); } else if (strcmp(name, "backoff") == 0) { - current_service->backoff = atoi(value); + service->backoff = atoi(value); } else { - printf("Unknown configuration entry \'%s\' in section \'%s\'\n", name, id); + printf("Unknown configuration entry \'%s\' in \'%s\'\n", name, section); } } else { printf("Unknown configuration section \'%s\'\n", section); @@ -109,12 +125,242 @@ static void init_signals() { sigaction(SIGPIPE, &sa, NULL); } +static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) { + const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS); + const char* client_ip = (char*)malloc(64); + uint16_t client_port = 0; + if (info != NULL) { + const struct sockaddr *addr = info->client_addr; + char ip_str[INET6_ADDRSTRLEN]; + + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str)); + client_port = ntohs(addr4->sin_port); + } else if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str)); + client_port = ntohs(addr6->sin6_port); + } + } + + uint16_t response_code = 0; + char* page; + size_t page_size = 0; + + if (strcmp(method, "GET") != 0 || strcmp(uri, "/") != 0) + response_code = 400; + + if (response_code != 200) { + mxml_node_t* doc = mxmlNewXML("1.0"); + if (strcmp(uri, "/") == 0 && response_code == 0) { + doc = cached_index; + response_code = 200; + } else { + mxml_node_t* html = mxmlNewElement(doc, "html"); + mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml"); + mxmlElementSetAttr(html, "xml:lang", "en"); + mxmlElementSetAttr(html, "lang", "en"); + + mxml_node_t* head = mxmlNewElement(html, "head"); + mxml_node_t *title = mxmlNewElement(head, "title"); + mxmlNewText(title, 0, daemon_name); + + mxml_node_t* meta_charset = mxmlNewElement(head, "meta"); + mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type"); + mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8"); + + mxml_node_t* meta_viewport = mxmlNewElement(head, "meta"); + mxmlElementSetAttr(meta_viewport, "name", "viewport"); + mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0"); + + mxml_node_t* body = mxmlNewElement(html, "body"); + + mxml_node_t* center = mxmlNewElement(body, "center"); + + mxml_node_t* error_header = mxmlNewElement(center, "h1"); + char err_str[5]; + snprintf(err_str, sizeof(err_str)-1, "%i", response_code); + mxmlNewText(error_header, 0, err_str); + } + page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK); + page_size = strlen(page); + } + + printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code); + fflush(stdout); + + struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page); + enum MHD_Result ret = MHD_queue_response(conn, response_code, response); + MHD_destroy_response(response); + return ret; +}; + +mxml_node_t* generate_index() { + mxml_node_t* doc = mxmlNewXML("1.0"); + mxml_node_t* html = mxmlNewElement(doc, "html"); + mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml"); + mxmlElementSetAttr(html, "xml:lang", "en"); + mxmlElementSetAttr(html, "lang", "en"); + + mxml_node_t* head = mxmlNewElement(html, "head"); + mxml_node_t *title = mxmlNewElement(head, "title"); + mxmlNewText(title, 0, daemon_name); + + mxml_node_t* meta_charset = mxmlNewElement(head, "meta"); + mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type"); + mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8"); + + mxml_node_t* meta_viewport = mxmlNewElement(head, "meta"); + mxmlElementSetAttr(meta_viewport, "name", "viewport"); + mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0"); + + if (web_stylesheet != 0 && strcmp(web_stylesheet, "") != 0) { + mxml_node_t* stylesheet = mxmlNewElement(head, "link"); + mxmlElementSetAttr(stylesheet, "rel", "stylesheet"); + mxmlElementSetAttr(stylesheet, "href", web_stylesheet); + } else { + mxml_node_t* stylesheet = mxmlNewElement(head, "style"); + mxmlNewText(stylesheet, 0, INTERNAL_STYLESHEET); + } + + mxml_node_t* body = mxmlNewElement(html, "body"); + + mxml_node_t* center = mxmlNewElement(body, "center"); + + if (web_banner != 0 && strlen(web_banner) > 0) { + mxml_node_t* banner = mxmlNewElement(center, "img"); + mxmlElementSetAttr(banner, "class", "banner"); + mxmlElementSetAttr(banner, "src", web_banner); + } + + mxml_node_t* header = mxmlNewElement(center, "h1"); + mxmlElementSetAttr(header, "class", "header"); + mxmlNewText(header, 0, daemon_name); + + int row_index = 0; + struct XMLServiceRow table_rows[services.size]; + + mxml_node_t* table = mxmlNewElement(center, "table"); + mxmlElementSetAttr(table, "width", "100%"); + + mxml_node_t* colgroup = mxmlNewElement(table, "colgroup"); + + int col_count = 1; + long now = microtime(); + long earliest_time = 0; + + Iterator count_it = vector_begin(&status_timeline); + Iterator peroid_end = vector_end(&status_timeline); + for (; !iterator_equals(&count_it, &peroid_end); iterator_increment(&count_it)) { + struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&count_it); + if (earliest_time == 0) earliest_time = peroid->time; + else if (peroid->time <= (now - (long)status_retention * 1000000)) { + earliest_time = peroid->time; + } else col_count++; + } + + Iterator service_it = vector_begin(&services); + Iterator service_end = vector_end(&services); + for (; !iterator_equals(&service_it, &service_end); iterator_increment(&service_it)) { + struct Service* service = *(struct Service**)iterator_get(&service_it); + mxml_node_t* header_row = mxmlNewElement(table, "tr"); + mxmlElementSetAttr(header_row, "class", "title"); + mxml_node_t* header_cell = mxmlNewElement(header_row, "td"); + mxmlElementSetAttr(header_cell, "align", "left"); + char* span_str = (char*)malloc(50); + snprintf(span_str, 50, "%d", col_count); + mxmlElementSetAttr(header_cell, "colspan", span_str); + + if (service->text != 0 && strlen(service->text) > 0) { + mxml_node_t* details = mxmlNewElement(header_cell, "details"); + mxmlElementSetAttr(details, "open", "1"); + mxml_node_t* summary = mxmlNewElement(details, "summary"); + if (service->icon != 0 && strlen(service->icon) > 0) { + mxml_node_t* icon = mxmlNewElement(summary, "img"); + mxmlElementSetAttr(icon, "src", service->icon); + } + mxml_node_t* title_bold = mxmlNewElement(summary, "b"); + mxmlNewText(title_bold, service->icon != 0, service->name); + mxmlNewText(details, 0, service->text); + } else { + if (service->icon != 0 && strlen(service->icon) > 0) { + mxml_node_t* icon = mxmlNewElement(header_cell, "img"); + mxmlElementSetAttr(icon, "src", service->icon); + } + mxml_node_t* title_bold = mxmlNewElement(header_cell, "b"); + mxmlNewText(title_bold, service->icon != 0, service->name); + } + + mxml_node_t* status_row = mxmlNewElement(table, "tr"); + table_rows[row_index] = (struct XMLServiceRow){ service, status_row }; + row_index++; + } + + long last_time = earliest_time; + long total_time = now - earliest_time; + Iterator peroid_it = vector_begin(&status_timeline); + for (; !iterator_equals(&peroid_it, &peroid_end); iterator_increment(&peroid_it)) { + struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&peroid_it); + if (peroid->time < earliest_time) continue; + + long duration = peroid->time - last_time; + if (total_time > 0 && duration > 0) { + double col_size = (double)duration / total_time * 100.0L; + char* size_str = (char*)malloc(50); + snprintf(size_str, 50, "%lf%%", col_size); + + mxml_node_t* col = mxmlNewElement(colgroup, "col"); + mxmlElementSetAttr(col, "width", size_str); + last_time = peroid->time; + } + + Iterator sit = vector_begin(&peroid->server_statuses); + Iterator send = vector_end(&peroid->server_statuses); + for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) { + struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit); + + struct XMLServiceRow* row_struct; + for (int x = 0; x < services.size; x++) + if (table_rows[x].service == srvstat->service) { + row_struct = &table_rows[x]; + break; + } + + if (row_struct->service != 0) { + mxml_node_t* cell = mxmlNewElement(row_struct->row, "td"); + mxmlElementSetAttr(cell, "_status", state_text(srvstat->state)); + mxmlElementSetAttrf(cell, "_ping", "%.02f ms", srvstat->ping); + + long peroid_time_s = peroid->time / 1000000; + char* time_str = (char*)malloc(20); + strftime(time_str, 20, "%a %b %d %I:%M %P", localtime(&peroid_time_s)); + mxmlElementSetAttr(cell, "_time", time_str); + + mxmlElementSetAttrf(cell, "_toptext", "%s - %.02f ms", state_text(srvstat->state), srvstat->ping); + mxmlElementSetAttr(cell, "_bottomtext", time_str); + } + } + } + mxmlNewElement(colgroup, "col"); + + mxmlNewElement(center, "hr"); + mxml_node_t* footer = mxmlNewElement(center, "p"); + mxmlElementSetAttr(footer, "class", "footer"); + mxmlNewText(footer, 0, "Powered by "); + mxml_node_t* prj_link = mxmlNewElement(footer, "a"); + mxmlElementSetAttr(prj_link, "href", "https://git.canithesis.org/Canithesis/stethoscope"); + mxmlNewText(prj_link, 0, "Stethoscope"); + + return doc; +} + int main(int argc, char** argv) { printf("Starting Stethoscope...\n"); fflush(stdout); init_signals(); - vector_setup(&status_timeline, 100, sizeof(struct StatusPeroid*)); + vector_setup(&status_timeline, 1000, sizeof(struct StatusPeroid*)); char* config_path; for (int c = 1; c < argc; c++) { @@ -138,7 +384,7 @@ int main(int argc, char** argv) { // Load configuration file vector_setup(&services, 255, sizeof(struct Service*)); printf("Loading configuration file: %s\n", config_path); - int err = ini_parse(config_path, config_handler, &services); + int err = ini_parse(config_path, config_handler, NULL); if (err < 0) { fprintf(stderr, "Failed to load configuration file: %i\n", err); return 1; @@ -156,14 +402,13 @@ int main(int argc, char** argv) { } vector_shrink_to_fit(&services); - struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, listen_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END); + struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, web_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END); if (daemon == NULL) { fprintf(stderr, "Failed to start HTTP daemon\n"); return 2; } - int last_doc_generated = 0; - running = 1; - while (daemon != NULL && running == 1) { + long last_doc_generated = 0; + while (daemon != NULL && daemon_enabled == 1) { time_t now = time(NULL); if (status_refresh > 0 && queued_time <= now - status_refresh) { queued_time = now; @@ -173,8 +418,8 @@ int main(int argc, char** argv) { report_queued = 0; report(1); } - if (last_doc_generated <= now - web_refresh || last_doc_generated < (queued_time / 1000000)) { - index_doc = generate_index(); + if (last_doc_generated <= now - web_recache || last_doc_generated < (queued_time / 1000000)) { + cached_index = generate_index(); last_doc_generated = now; } sleep(1); diff --git a/src/main.h b/src/main.h index 442ef42..4452d43 100644 --- a/src/main.h +++ b/src/main.h @@ -15,300 +15,14 @@ #include "util.h" #include "services.c" -#define PAGE_INDEX 0 -#define PAGE_ASSET 1 -#define PAGE_ERROR 2 - -mxml_node_t* index_doc; - -uint8_t running = 0; -char* daemon_name = "Stethoscope"; -uint16_t listen_port = 7800; -char* webroot = "share/"; -char* daemon_banner = ""; -uint32_t global_backoff = 15; -uint32_t web_refresh = 15; -uint32_t status_refresh = 30; -uint32_t retention = 86400; - -int get_asset(char** buffer, const char* path, size_t* size) { - *size = 0; - if (strlen(webroot) + strlen(path) + 2 > PATH_MAX) - return 404; - - char full_path[PATH_MAX] = {}; - snprintf(full_path, sizeof(full_path), "%s%s", webroot, path); - - char resolved_path[PATH_MAX] = {}; - if (realpath(full_path, resolved_path) == NULL) - return 404; - - char resolved_base[PATH_MAX] = {}; - if (realpath(webroot, resolved_base) == NULL) - return 404; - - size_t resolved_base_len = strlen(resolved_base); - if (strncmp(resolved_path, resolved_base, resolved_base_len) != 0) - return 404; - - if (resolved_path[resolved_base_len] != '\0' && resolved_path[resolved_base_len] != '/') - return 404; - - FILE* asset = fopen(resolved_path, "rb"); - if (!asset) return 404; - - fseek(asset, 0, SEEK_END); - *size = ftell(asset); - rewind(asset); - - *buffer = (char*)malloc(*size + 1); - if (!*buffer) { - fclose(asset); - *size = 0; - return 500; - } - - size_t bytes_read = fread(*buffer, 1, *size, asset); - fclose(asset); - - if (bytes_read != *size) { - free(*buffer); - *size = 0; - return 500; - } - - return 200; -}; - struct XMLServiceRow { struct Service* service; mxml_node_t* row; }; -static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) { - const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS); - const char* client_ip = (char*)malloc(64); - uint16_t client_port = 0; - if (info != NULL) { - const struct sockaddr *addr = info->client_addr; - char ip_str[INET6_ADDRSTRLEN]; +#define INTERNAL_STYLESHEET \ + "td[_status=UP]{background-color:green;}\n" \ + "td[_status=PARTIAL]{background-color:yellow;}" \ + "td[_status=DOWN]{background-color:red;}" - if (addr->sa_family == AF_INET) { - const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; - client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str)); - client_port = ntohs(addr4->sin_port); - } else if (addr->sa_family == AF_INET6) { - const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; - client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str)); - client_port = ntohs(addr6->sin6_port); - } - } - - uint16_t response_code = 0; - char* page; - size_t page_size = 0; - - if (strcmp(method, "GET") == 0) { - if (strcmp(uri, "/") != 0) - response_code = get_asset(&page, uri, &page_size); - } else response_code = 400; - - if (response_code != 200) { - mxml_node_t* doc = mxmlNewXML("1.0"); - if (strcmp(uri, "/") == 0 && response_code == 0) { - doc = index_doc; - response_code = 200; - } else { - mxml_node_t* html = mxmlNewElement(doc, "html"); - mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml"); - mxmlElementSetAttr(html, "xml:lang", "en"); - mxmlElementSetAttr(html, "lang", "en"); - - mxml_node_t* head = mxmlNewElement(html, "head"); - mxml_node_t *title = mxmlNewElement(head, "title"); - mxmlNewText(title, 0, daemon_name); - - mxml_node_t* meta_charset = mxmlNewElement(head, "meta"); - mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type"); - mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8"); - - mxml_node_t* meta_viewport = mxmlNewElement(head, "meta"); - mxmlElementSetAttr(meta_viewport, "name", "viewport"); - mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0"); - - mxml_node_t* body = mxmlNewElement(html, "body"); - - mxml_node_t* center = mxmlNewElement(body, "center"); - - mxml_node_t* error_header = mxmlNewElement(center, "h1"); - char err_str[5]; - snprintf(err_str, sizeof(err_str)-1, "%i", response_code); - mxmlNewText(error_header, 0, err_str); - } - page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK); - page_size = strlen(page); - } - - printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code); - fflush(stdout); - - struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page); - enum MHD_Result ret = MHD_queue_response(conn, response_code, response); - MHD_destroy_response(response); - return ret; -}; - -mxml_node_t* generate_index() { - mxml_node_t* doc = mxmlNewXML("1.0"); - mxml_node_t* html = mxmlNewElement(doc, "html"); - mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml"); - mxmlElementSetAttr(html, "xml:lang", "en"); - mxmlElementSetAttr(html, "lang", "en"); - - mxml_node_t* head = mxmlNewElement(html, "head"); - mxml_node_t *title = mxmlNewElement(head, "title"); - mxmlNewText(title, 0, daemon_name); - - mxml_node_t* meta_charset = mxmlNewElement(head, "meta"); - mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type"); - mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8"); - - mxml_node_t* meta_viewport = mxmlNewElement(head, "meta"); - mxmlElementSetAttr(meta_viewport, "name", "viewport"); - mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0"); - - mxml_node_t* stylesheet = mxmlNewElement(head, "link"); - mxmlElementSetAttr(stylesheet, "rel", "stylesheet"); - mxmlElementSetAttr(stylesheet, "href", "style.css"); - - mxml_node_t* body = mxmlNewElement(html, "body"); - - mxml_node_t* center = mxmlNewElement(body, "center"); - - if (daemon_banner != 0 && strlen(daemon_banner) > 0) { - mxml_node_t* banner = mxmlNewElement(center, "img"); - mxmlElementSetAttr(banner, "class", "banner"); - mxmlElementSetAttr(banner, "src", daemon_banner); - } - - mxml_node_t* header = mxmlNewElement(center, "h1"); - mxmlElementSetAttr(header, "class", "header"); - mxmlNewText(header, 0, daemon_name); - - int row_index = 0; - struct XMLServiceRow table_rows[services.size]; - - mxml_node_t* table = mxmlNewElement(center, "table"); - mxmlElementSetAttr(table, "width", "100%"); - - mxml_node_t* colgroup = mxmlNewElement(table, "colgroup"); - - int col_count = 1; - long now = microtime(); - long earliest_time = 0; - - Iterator count_it = vector_begin(&status_timeline); - Iterator peroid_end = vector_end(&status_timeline); - for (; !iterator_equals(&count_it, &peroid_end); iterator_increment(&count_it)) { - struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&count_it); - if (earliest_time == 0) earliest_time = peroid->time; - else if (peroid->time <= (now - (long)retention * 1000000)) { - earliest_time = peroid->time; - } else col_count++; - } - - Iterator service_it = vector_begin(&services); - Iterator service_end = vector_end(&services); - for (; !iterator_equals(&service_it, &service_end); iterator_increment(&service_it)) { - struct Service* service = *(struct Service**)iterator_get(&service_it); - mxml_node_t* header_row = mxmlNewElement(table, "tr"); - mxmlElementSetAttr(header_row, "class", "title"); - mxml_node_t* header_cell = mxmlNewElement(header_row, "td"); - mxmlElementSetAttr(header_cell, "align", "left"); - char* span_str = (char*)malloc(50); - snprintf(span_str, 50, "%d", col_count); - mxmlElementSetAttr(header_cell, "colspan", span_str); - - if (service->text != 0 && strlen(service->text) > 0) { - mxml_node_t* details = mxmlNewElement(header_cell, "details"); - mxmlElementSetAttr(details, "open", "1"); - mxml_node_t* summary = mxmlNewElement(details, "summary"); - if (service->icon != 0 && strlen(service->icon) > 0) { - mxml_node_t* icon = mxmlNewElement(summary, "img"); - mxmlElementSetAttr(icon, "src", service->icon); - } - mxml_node_t* title_bold = mxmlNewElement(summary, "b"); - mxmlNewText(title_bold, service->icon != 0, service->name); - mxmlNewText(details, 0, service->text); - } else { - if (service->icon != 0 && strlen(service->icon) > 0) { - mxml_node_t* icon = mxmlNewElement(header_cell, "img"); - mxmlElementSetAttr(icon, "src", service->icon); - } - mxml_node_t* title_bold = mxmlNewElement(header_cell, "b"); - mxmlNewText(title_bold, service->icon != 0, service->name); - } - - mxml_node_t* status_row = mxmlNewElement(table, "tr"); - table_rows[row_index] = (struct XMLServiceRow){ service, status_row }; - row_index++; - } - - long last_time = earliest_time; - long total_time = now - earliest_time; - Iterator peroid_it = vector_begin(&status_timeline); - for (; !iterator_equals(&peroid_it, &peroid_end); iterator_increment(&peroid_it)) { - struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&peroid_it); - if (peroid->time < earliest_time) continue; - - long duration = peroid->time - last_time; - if (total_time > 0 && duration > 0) { - double col_size = (double)duration / total_time * 100.0L; - char* size_str = (char*)malloc(50); - snprintf(size_str, 50, "%lf%%", col_size); - - mxml_node_t* col = mxmlNewElement(colgroup, "col"); - mxmlElementSetAttr(col, "width", size_str); - last_time = peroid->time; - } - - Iterator sit = vector_begin(&peroid->server_statuses); - Iterator send = vector_end(&peroid->server_statuses); - for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) { - struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit); - - struct XMLServiceRow* row_struct; - for (int x = 0; x < services.size; x++) - if (table_rows[x].service == srvstat->service) { - row_struct = &table_rows[x]; - break; - } - - if (row_struct->service != 0) { - mxml_node_t* cell = mxmlNewElement(row_struct->row, "td"); - mxmlElementSetAttr(cell, "_status", state_text(srvstat->state)); - mxmlElementSetAttrf(cell, "_ping", "%.02f ms", srvstat->ping); - - long peroid_time_s = peroid->time / 1000000; - char* time_str = (char*)malloc(20); - strftime(time_str, 20, "%a %b %d %I:%M %P", localtime(&peroid_time_s)); - mxmlElementSetAttr(cell, "_time", time_str); - - mxmlElementSetAttrf(cell, "_toptext", "%s - %.02f ms", state_text(srvstat->state), srvstat->ping); - mxmlElementSetAttr(cell, "_bottomtext", time_str); - } - } - } - mxmlNewElement(colgroup, "col"); - - mxmlNewElement(center, "hr"); - mxml_node_t* footer = mxmlNewElement(center, "p"); - mxmlElementSetAttr(footer, "class", "footer"); - mxmlNewText(footer, 0, "Powered by "); - mxml_node_t* prj_link = mxmlNewElement(footer, "a"); - mxmlElementSetAttr(prj_link, "href", "https://git.canithesis.org/Canithesis/stethoscope"); - mxmlNewText(prj_link, 0, "Stethoscope"); - - return doc; -} #endif \ No newline at end of file diff --git a/src/services.c b/src/services.c index c990abf..b1f48c7 100644 --- a/src/services.c +++ b/src/services.c @@ -346,6 +346,17 @@ void deinit_service(struct Service** self) { } } +struct Service* get_service_of_id_or_null(const char* id) { + Iterator it = vector_begin(&services); + Iterator end = vector_end(&services); + for (; !iterator_equals(&it, &end); iterator_increment(&it)) { + struct Service* current = *(struct Service**)iterator_get(&it); + if (strcmp(id, current->id) == 0) + return current; + } + return NULL; +} + const char* state_text(enum State state) { switch (state) { case NONE: return "NONE";