\n"
" \n"
" Clients\n"
" %d registered", result->clients_len);
if (stale_count > 0) {
fprintf(f, "%d stale", stale_count);
}
fprintf(f,
"\n"
"
\n"
" \n"
" New Client
\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"
" View →\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)
);
}
fprintf(f,
"
\n"
" \n"
"\n"
"\n");
fclose(f);
*out_len = buf_len;
return buf;
}
char *html_render_client_detail_page(const struct Request *request, 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"
" ← 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://%s/api/report \\\n"
" -H \"Content-Type: application/json\" \\\n"
" -d \'{\"client\": \"%s\", \"secret\": \"%s\", \"event\": \"begin_backup\"}\'"
"
\n"
"
\n"
" \n",
client_result->secret,
request->proto != NULL ? request->proto : "http",
request->host != NULL ? request->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;
bool duration_valid = false;
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) {
duration_valid = true;
badge_class = "fail";
badge_text = "Failed";
} else {
duration_valid = true;
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,
duration_valid ? 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"
"\n");
fclose(f);
*out_len = buf_len;
return buf;
}