#include "html.h" #include "mytime.h" #include #include static void emit_stylesheet(FILE *f) { fprintf(f, "\n" ); } static void mask_secret(const char *secret, char *out, size_t out_len) { size_t len = strlen(secret); if (len >= 8 && out_len >= 13) { snprintf(out, out_len, "%.4s....%.4s", secret, secret + len - 4); } else if (len > 0) { snprintf(out, out_len, "****"); } else { out[0] = '\0'; } } static const char *status_to_badge_class(const char *status) { if (strcmp(status, "OK") == 0) return "ok"; if (strcmp(status, "RUNNING") == 0) return "run"; if (strcmp(status, "STALE") == 0) return "stale"; if (strcmp(status, "INTERRUPTED") == 0) return "fail"; if (strcmp(status, "FINISHED") == 0) return "ok"; return "ok"; } static const char *status_to_display(const char *status) { if (strcmp(status, "OK") == 0) return "OK"; if (strcmp(status, "RUNNING") == 0) return "Running"; if (strcmp(status, "STALE") == 0) return "Stale"; if (strcmp(status, "INTERRUPTED") == 0) return "Failed"; if (strcmp(status, "FINISHED") == 0) return "Done"; return status; } char *html_render_clients_page(const char *host, const struct ListClientResult *result, size_t *out_len) { // Count stale clients int stale_count = 0; for (int i = 0; i < result->clients_len; i++) { if (result->clients[i].is_stale) stale_count++; } char *buf = NULL; size_t buf_len = 0; FILE *f = open_memstream(&buf, &buf_len); if (!f) { return NULL; } fprintf(f, "\n" "\n" "\n" " \n" " \n" " BorgFlag - Clients\n"); emit_stylesheet(f); fprintf(f, "\n" "\n" "
\n" " BorgFlag\n" "
\n" "
\n" "
\n" "

\n" " Clients\n" " %d registered", result->clients_len); if (stale_count > 0) { fprintf(f, "%d stale", stale_count); } fprintf(f, "\n" " \n" "

\n" "
\n" "
\n" " \n" " \n" "
\n" " \n" "
\n" "
\n" "
\n" " Name\n" " Secret\n" " Last Backup\n" " Status\n" " \n" "
\n" ); for (int i = 0; i < result->clients_len; i++) { char masked_secret[16]; mask_secret(result->clients[i].secret, masked_secret, sizeof(masked_secret)); fprintf(f, " \n" " %s\n" " %s\n" " %s\n" " %s\n" " \n" " \n", result->clients[i].id, result->clients[i].is_stale ? "stale" : "", result->clients[i].name, masked_secret, result->clients[i].last_backup.tv_sec > 0.0 ? format_time(result->clients[i].last_backup).buf : "-", status_to_badge_class(result->clients[i].status), status_to_display(result->clients[i].status), result->clients[i].id ); } fprintf(f, "
\n" "
\n" "\n" "\n"); fclose(f); *out_len = buf_len; return buf; } char *html_render_client_detail_page(const char *host, const struct GetClientOnlyResult *client_result, const struct GetBackupsResult *backups_result, size_t *out_len) { // Determine status and staleness int is_stale = 0; const char *current_status = "OK"; if (backups_result->backups_len > 0) { const char *last_status = backups_result->backups[0].status; if (strcmp(last_status, "RUNNING") == 0) { current_status = "RUNNING"; } else if (strcmp(last_status, "INTERRUPTED") == 0) { current_status = "INTERRUPTED"; } } char *buf = NULL; size_t buf_len = 0; FILE *f = open_memstream(&buf, &buf_len); if (!f) { return NULL; } fprintf(f, "\n" "\n" "\n" " \n" " \n" " BorgFlag - %s\n", client_result->name); emit_stylesheet(f); fprintf(f, "\n" "\n" "
\n" " BorgFlag\n" "
\n" "
\n" "
\n" " ← Back to clients\n" " \n" "

%s

\n" "

%d backups %s

\n", is_stale ? " class=\"stale\"" : "", client_result->name, backups_result->backups_len, status_to_badge_class(current_status), status_to_display(current_status) ); if (is_stale) { fprintf(f, "

Warning: Last backup was more than 24 hours ago. Expected every 24 hours.

\n" ); } fprintf(f, " Secret: %s\n" " \n" "
\n" " How To\n" "
\n" "
curl -s -X POST %s \\\n"
		"    -H \"Content-Type: application/json\" \\\n"
		"    -d \'{\"client\": \"%s\", \"secret\": \"%s\", \"event\": \"begin_backup\"}\'"
		        "
\n" "
\n" "
\n", client_result->secret, host ? host : "localhost", client_result->name, client_result->secret ); fprintf(f, "

Recent Backups

\n"); if(backups_result->backups_len == 0) { fprintf(f, "

No backups recorded.

\n"); } else { fprintf(f, "
\n"); for (int i = 0; i < backups_result->backups_len; i++) { const char *badge_class; const char *badge_text; if (strcmp(backups_result->backups[i].status, "RUNNING") == 0) { badge_class = "run"; badge_text = "Running"; } else if (strcmp(backups_result->backups[i].status, "INTERRUPTED") == 0) { badge_class = "fail"; badge_text = "Failed"; } else { badge_class = "ok"; badge_text = "Done"; } struct timespec duration = time_sub(&backups_result->backups[i].completed, &backups_result->backups[i].started); fprintf(f, "
%s%s
\n", format_time(backups_result->backups[i].started).buf, format_duration(duration).buf, badge_class, badge_text ); } fprintf(f, "
\n"); } fprintf(f, "
\n" "\n" "\n"); fclose(f); *out_len = buf_len; return buf; } char *html_render_login_page(const char *error_msg, size_t *out_len) { char *buf = NULL; size_t buf_len = 0; FILE *f = open_memstream(&buf, &buf_len); if (!f) return NULL; fprintf(f, "\n" "\n" "\n" " \n" " \n" " BorgFlag - Login\n"); emit_stylesheet(f); fprintf(f, "\n" "\n" "
\n" "
\n" "

BorgFlag

\n"); if (error_msg) { fprintf(f, "
%s
\n", error_msg); } fprintf(f, "
\n" " \n" " \n" "
\n" "
\n" " \n" " \n" "
\n" " \n" "
\n" "
\n" "\n" "\n"); fclose(f); *out_len = buf_len; return buf; }