diff --git a/src/main.c b/src/main.c index 650fdca..ea11a49 100644 --- a/src/main.c +++ b/src/main.c @@ -21,6 +21,7 @@ static void signal_handler(int signo) { struct PreService { char* id; char* name; + char* icon; char* text; char* endpoints; uint32_t backoff; @@ -34,12 +35,19 @@ static int config_handler(void* user, const char* section, const char* name, con } 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, "backoff") == 0) { - int val = atoi(value); - if (val < 1) val = 1; - global_backoff = val; + 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 { printf("Unknown configuration entry \'%s\'\n", name); } @@ -70,6 +78,9 @@ static int config_handler(void* user, const char* section, const char* name, con } else if (strcmp(name, "text") == 0) { current_service->text = malloc(strlen(value)+1); strcpy(current_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) { current_service->endpoints = malloc(strlen(value)+1); strcpy(current_service->endpoints, value); @@ -146,6 +157,7 @@ int main(int argc, char** argv) { struct Service* service = malloc(8192); service->id = preservice->id; service->name = preservice->name; + service->icon = preservice->icon; service->text = preservice->text; service->last_state = DOWN; service->backoff = preservice->backoff == 0 ? global_backoff : preservice->backoff; @@ -171,15 +183,34 @@ int main(int argc, char** argv) { fprintf(stderr, "Failed to start HTTP daemon\n"); return 2; } + int last_doc_generated = 0; running = 1; while (daemon != NULL && running == 1) { + time_t now = time(NULL); + if (queued_time <= now - status_refresh) { + queued_time = now; + report_queued = 1; + } if (report_queued > 0) { report_queued = 0; report(); - } else sleep(1); + } + if (last_doc_generated <= now - web_refresh) { + index_doc = generate_index(); + last_doc_generated = now; + } + sleep(1); } printf("\nShutting down services...\n"); fflush(stdout); MHD_stop_daemon(daemon); + printf("Waiting for services to shut down..."); + fflush(stdout); + 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); + deinit_service(&service); + } return 0; } \ No newline at end of file diff --git a/src/main.h b/src/main.h index c48ef03..0c0f7d7 100644 --- a/src/main.h +++ b/src/main.h @@ -19,38 +19,39 @@ #define PAGE_ASSET 1 #define PAGE_ERROR 2 -mxml_node_t* doc; +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 = 5; - -char* cached_index; +uint32_t web_refresh = 30; +uint32_t status_refresh = 30; int get_asset(char** buffer, const char* path, size_t* size) { *size = 0; if (strlen(webroot) + strlen(path) + 2 > PATH_MAX) - return 403; + 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 403; + return 404; char resolved_base[PATH_MAX]; if (realpath(webroot, resolved_base) == NULL) - return 403; + return 404; size_t resolved_base_len = strlen(resolved_base); if (strncmp(resolved_path, resolved_base, resolved_base_len) != 0) - return 400; + return 404; if (resolved_path[resolved_base_len] != '\0' && resolved_path[resolved_base_len] != '/') - return 400; + return 404; FILE* asset = fopen(resolved_path, "rb"); if (!asset) return 404; @@ -115,120 +116,36 @@ static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, con if (response_code != 200) { 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 (strcmp(uri, "/") == 0 && response_code == 0) { - mxml_node_t* header = mxmlNewElement(center, "h1"); - 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"); - - 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"); - mxml_node_t* header_cell = mxmlNewElement(table, "th"); - mxmlElementSetAttr(header_cell, "align", "left"); - mxmlNewText(header_cell, 0, service->name); - - mxml_node_t* status_row = mxmlNewElement(table, "tr"); - - table_rows[row_index] = (struct XMLServiceRow){ service, status_row }; - row_index++; - } - - 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 - 86400000000)) { - earliest_time = peroid->time; - } else col_count++; - } - - 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)); - char* ping_str = (char*)malloc(50); - snprintf(ping_str, 50, "%f ms", srvstat->ping); - mxmlElementSetAttr(cell, "_ping", ping_str); - } - } - } - + 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); } @@ -242,4 +159,146 @@ static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, con 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, "src", daemon_banner); + } + + mxml_node_t* header = mxmlNewElement(center, "h1"); + 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 - 86400000000)) { + 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 / 1000; + 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"); + return doc; +} #endif \ No newline at end of file