Have server generate index instead, add service icons and banner, add config variables to handle web refresh and status refresh, properly shut down services on close

This commit is contained in:
2026-04-21 02:25:24 -05:00
parent 75c3f063cf
commit 1e370fcf2e
2 changed files with 208 additions and 118 deletions

View File

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

View File

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