From 1b19afaa339f2f68520d61b634600ddeb04effb7 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sat, 21 Feb 2026 11:14:42 +0100 Subject: Split up the rendering from the logic --- src/web.c | 1312 +++---------------------------------------------------------- 1 file changed, 50 insertions(+), 1262 deletions(-) (limited to 'src/web.c') diff --git a/src/web.c b/src/web.c index 6dd18e1..9c0789b 100644 --- a/src/web.c +++ b/src/web.c @@ -1,21 +1,15 @@ #include "web.h" +#include "app.h" +#include "html.h" #include "mytime.h" #include -#include -#include -#include -#include -#include #include -#include #include #include #include #include -#include #include -#include #include #include #include @@ -23,11 +17,6 @@ #define PAGE "libmicrohttpd demo"\ "libmicrohttpd demo" -static void sqliteError(void *pArg, int iErrCode, const char *zMsg) { - // @CLEANUP: We should stuff these somewhere, but for now just print it - fprintf(stderr, "(%d) %s\n", iErrCode, zMsg); -} - static int generate_secret(char *out) { unsigned char bytes[16]; int fd = open("/dev/urandom", O_RDONLY); @@ -64,1243 +53,6 @@ static int is_authenticated(struct App *app, struct MHD_Connection *connection) return strcmp(cookie, expected) == 0; } -// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix -static void _mkdir(const char *dir) { - char tmp[PATH_MAX]; - char *p = NULL; - size_t len; - int rc; - - snprintf(tmp, sizeof(tmp),"%s",dir); - len = strlen(tmp); - if (tmp[len - 1] == '/') - tmp[len - 1] = 0; - for (p = tmp + 1; *p; p++) - if (*p == '/') { - *p = 0; - rc = mkdir(tmp, S_IRWXU); - if(rc != 0 && errno != EEXIST) { - abort(); - } - *p = '/'; - } - rc = mkdir(tmp, S_IRWXU); - if(rc != 0 && errno != EEXIST) { - abort(); - } - -} - -void prepare_database(struct App *app) { - // @CLEANUP: Move this to main? doesn't belong here at least - sqlite3_config(SQLITE_CONFIG_LOG, sqliteError, NULL); - sqlite3_config(SQLITE_CONFIG_URI, 1); - - int err; - - printf("Running migrations against %s\n", app->dbname); - - if(memcmp("file::memory", app->dbname, 12) != 0) { - char *full = strdup(app->dbname); - char *dbdir = dirname(full); - _mkdir(dbdir); - free(full); - } - - sqlite3 *conn; - if((err = sqlite3_open(app->dbname, &conn)) != SQLITE_OK) { - fprintf(stderr, "(%d) %s\n", err, sqlite3_errmsg(conn)); - abort(); - } - sqlite3_db_config(conn, SQLITE_DBCONFIG_ENABLE_FKEY, 1, NULL); - - if(sqlite3_exec(conn, "CREATE TABLE IF NOT EXISTS migration (id INTEGER PRIMARY KEY NOT NULL)", NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT MAX(id) FROM migration", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - - uint64_t maxVersion = 0; - while((err = sqlite3_step(stmt)) == SQLITE_ROW) { - maxVersion = sqlite3_column_int64(stmt, 0); - } - if(err != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(maxVersion < 1) { - char *prog = - "BEGIN TRANSACTION;" - "CREATE TABLE backups (" - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - "client TEXT NOT NULL, " - "status TEXT NOT NULL, " - "started TEXT NOT NULL, " - "completed TEXT " - ") STRICT"; - if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - - if(sqlite3_bind_int64(stmt, 1, 1) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - } - - if(maxVersion < 2) { - char *prog = - "BEGIN TRANSACTION;" - "CREATE TABLE clients (" - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - "name TEXT NOT NULL " - ") STRICT;" - "INSERT INTO clients(name) " - "SELECT b.client " - "FROM backups b " - "GROUP BY b.client;" - "CREATE TABLE backups_next (" - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - "client INTEGER NOT NULL, " - "status TEXT NOT NULL, " - "started TEXT NOT NULL, " - "completed TEXT ," - "FOREIGN KEY(client) REFERENCES clients(id)" - ") STRICT;" - "INSERT INTO backups_next(id, client, status, started, completed) " - "SELECT b.id, c.id, b.status, b.started, b.completed " - "FROM backups b " - "JOIN clients c ON b.client = c.name;" - "DROP TABLE backups;" - "ALTER TABLE backups_next RENAME TO backups;"; - if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - - if(sqlite3_bind_int64(stmt, 1, 2) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - } - - if(maxVersion < 3) { - char *prog = - "BEGIN TRANSACTION;" - "CREATE UNIQUE INDEX clients_name ON clients(name)"; - if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - - if(sqlite3_bind_int64(stmt, 1, 3) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - } - - if(maxVersion < 4) { - char *prog = - "BEGIN TRANSACTION;" - "ALTER TABLE clients ADD COLUMN secret TEXT NOT NULL DEFAULT ''"; - if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - - if(sqlite3_bind_int64(stmt, 1, 4) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { - abort(); - } - } -} - -enum ReportEvent { - REV_BEGIN, - REV_FINISH, -}; - -// @CLEANUP: This should live somewhere else, but I don't have a spot for it yet. -// Returns 0 on success, -1 on error, -2 on secret mismatch -int submit_report(struct App *app, char *client, char *secret, enum ReportEvent event) { - sqlite3 *conn; - int err; - if((err = sqlite3_open(app->dbname, &conn)) != SQLITE_OK) { - abort(); - } - - struct timespec now = get_current_time(); - - int64_t clientId; - { - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id, secret FROM clients WHERE name = ?1", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_text(stmt, 1, client, -1, SQLITE_STATIC) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_ROW) { - abort(); - } - - if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { - abort(); - } - clientId = sqlite3_column_int64(stmt, 0); - - const char *stored_secret = (const char *)sqlite3_column_text(stmt, 1); - if(stored_secret == NULL || strcmp(stored_secret, secret) != 0) { - sqlite3_finalize(stmt); - sqlite3_close(conn); - return -2; - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - } - - switch(event) { - case REV_BEGIN: { - sqlite3_stmt *stmt; - - // Mark any existing RUNNING backup as INTERRUPTED - if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = 'INTERRUPTED', completed = datetime(?1, 'unixepoch', 'subsec') WHERE client = ?2 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_double(stmt, 1, TIME_AS_FLOAT(now)) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 2, clientId) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - stmt = NULL; - - // Insert the new backup - if(sqlite3_prepare_v2(conn, "INSERT INTO backups (client, status, started) VALUES (?1, ?2, datetime(?3, 'unixepoch', 'subsec'))", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_text(stmt, 2, "RUNNING", -1, SQLITE_STATIC) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_double(stmt, 3, TIME_AS_FLOAT(now)) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - break; - } - case REV_FINISH: { - uint64_t backupId; - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id FROM backups WHERE client = ?1 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_ROW) { - abort(); - } - - if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { - abort(); - } - backupId = sqlite3_column_int64(stmt, 0); - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - stmt = NULL; - - if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = ?1, completed = datetime(?2, 'unixepoch', 'subsec') WHERE id = ?3", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_text(stmt, 1, "FINISHED", -1, SQLITE_STATIC) != SQLITE_OK) { - abort(); - } - - if(sqlite3_bind_double(stmt, 2, TIME_AS_FLOAT(now)) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 3, backupId) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - break; - } - } - - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -int create_client(struct Request *req, char *name, struct CreateClientResult *result) { - sqlite3 *conn; - int err; - if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { - abort(); - } - - if(generate_secret(result->secret) != 0) { - abort(); - } - - { - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO clients (name, secret) VALUES (?1, ?2)", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_text(stmt, 2, result->secret, -1, SQLITE_STATIC) != SQLITE_OK) { - abort(); - } - - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - - result->id = sqlite3_last_insert_rowid(conn); - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - } - - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -int delete_client(struct Request *req, int64_t client_id) { - sqlite3 *conn; - if(sqlite3_open(req->server->app->dbname, &conn) != SQLITE_OK) { - abort(); - } - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "DELETE FROM clients WHERE id = ?1", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { - abort(); - } - if(sqlite3_step(stmt) != SQLITE_DONE) { - abort(); - } - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -int list_clients(struct Request *req, struct ListClientResult *result) { - sqlite3 *conn; - int err; - if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { - abort(); - } - - { - sqlite3_stmt *stmt; - const char *sql = - "SELECT c.id, c.name, c.secret, unixepoch(b_success.completed,'subsec'), b_last.status " - "FROM clients c " - "LEFT JOIN backups b_success ON b_success.client = c.id " - " AND b_success.id = (SELECT id FROM backups WHERE client = c.id AND status = 'FINISHED' ORDER BY id DESC LIMIT 1) " - "LEFT JOIN backups b_last ON b_last.client = c.id " - " AND b_last.id = (SELECT id FROM backups WHERE client = c.id ORDER BY id DESC LIMIT 1) " - "WHERE c.id > ?1 ORDER BY c.id ASC LIMIT ?2"; - if(sqlite3_prepare_v2(conn, sql, -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, -1) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 2, 16) != SQLITE_OK) { - abort(); - } - - int ret; - int index = 0; - while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { - assert(index < 16); - - if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { - abort(); - } - - int64_t clientId = sqlite3_column_int64(stmt, 0); - const uint8_t *name = sqlite3_column_text(stmt, 1); - const uint8_t *secret = sqlite3_column_text(stmt, 2); - const double last_backup = sqlite3_column_double(stmt, 3); - const uint8_t *backup_status = sqlite3_column_text(stmt, 4); - - result->clients[index].id = clientId; - strncpy(result->clients[index].name, (char*)name, CLIENT_NAME_MAX); - strncpy(result->clients[index].secret, secret ? (char*)secret : "", CLIENT_SECRET_LEN); - result->clients[index].last_backup = time_from_double(last_backup); - - if (backup_status == NULL) { - strncpy(result->clients[index].status, "MISSING", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } else if (strcmp((char*)backup_status, "RUNNING") == 0) { - strncpy(result->clients[index].status, "RUNNING", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } else if (strcmp((char*)backup_status, "INTERRUPTED") == 0) { - strncpy(result->clients[index].status, "INTERRUPTED", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } else { - strncpy(result->clients[index].status, "OK", BACKUP_STATUS_MAX); - result->clients[index].is_stale = 0; - } - - index++; - } - if(ret != SQLITE_DONE) { - abort(); - } - - result->clients_len = index; - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - } - - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -int get_client(struct Request *req, int64_t client_id, struct GetClientOnlyResult *result) { - sqlite3 *conn; - int err; - if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { - abort(); - } - - result->found = 0; - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id, name, secret FROM clients WHERE id = ?1", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { - abort(); - } - - int ret = sqlite3_step(stmt); - if(ret == SQLITE_ROW) { - result->found = 1; - result->id = sqlite3_column_int64(stmt, 0); - const uint8_t *name = sqlite3_column_text(stmt, 1); - const uint8_t *secret = sqlite3_column_text(stmt, 2); - strncpy(result->name, (char*)name, CLIENT_NAME_MAX); - strncpy(result->secret, (char*)secret, CLIENT_SECRET_LEN); - } else if(ret != SQLITE_DONE) { - abort(); - } - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -int get_client_backups(struct Request *req, int64_t client_id, struct GetBackupsResult *result) { - sqlite3 *conn; - int err; - if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { - abort(); - } - - uint16_t limit = result->backups_len; - - sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id, status, unixepoch(started, 'subsec'), unixepoch(completed, 'subsec') FROM backups WHERE client = ?1 ORDER BY started DESC LIMIT ?2", -1, &stmt, NULL) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { - abort(); - } - if(sqlite3_bind_int(stmt, 2, limit) != SQLITE_OK) { - abort(); - } - - int ret; - int index = 0; - while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { - if(index >= limit) break; - - result->backups[index].id = sqlite3_column_int64(stmt, 0); - - const uint8_t *status = sqlite3_column_text(stmt, 1); - const double started = sqlite3_column_double(stmt, 2); - const double completed = sqlite3_column_double(stmt, 3); - - strncpy(result->backups[index].status, status ? (char*)status : "", BACKUP_STATUS_MAX); - result->backups[index].started = time_from_double(started); - result->backups[index].completed = time_from_double(completed); - - index++; - } - if(ret != SQLITE_DONE && ret != SQLITE_ROW) { - abort(); - } - - result->backups_len = index; - - if(sqlite3_finalize(stmt) != SQLITE_OK) { - abort(); - } - - if(sqlite3_close(conn) != SQLITE_OK) { - abort(); - } - return 0; -} - -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; -} - -static char *render_clients_page(struct Request *request, size_t *out_len) { - int ret; - struct ListClientResult *result = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16); - ret = list_clients(request, result); - if(ret != 0) { - free(result); - return NULL; - } - - // 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) { - free(result); - 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" - " Name\n" - " Secret\n" - " Last Backup\n" - " Status\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 ? " class=\"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; - free(result); - return buf; -} - -static char *render_client_detail_page(struct Request *request, int64_t client_id, size_t *out_len) { - struct GetClientOnlyResult client_result; - int ret = get_client(request, client_id, &client_result); - if(ret != 0) { - return NULL; - } - - if(!client_result.found) { - return NULL; - } - - struct GetBackupsResult *backups_result = malloc(sizeof(struct GetBackupsResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY); - backups_result->backups_len = MAX_BACKUPS_DISPLAY; - ret = get_client_backups(request, client_id, backups_result); - if(ret != 0) { - free(backups_result); - return NULL; - } - - // 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) { - free(backups_result); - return NULL; - } - - fprintf(f, - "\n" - "\n" - "\n" - " \n" - " \n" - " BorgFlag - %s\n", client_result.name); - emit_stylesheet(f); - fprintf(f, - "\n" - "\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" - " API Snippets\n" - "
\n" - "
\n" - "

Start backup

\n" - "
curl -X POST http://%s/api/report \\\n"
-		"     -H \"Content-Type: application/json\" \\\n"
-		"     -d '{\n"
-		"           \"client\": \"%s\",\n"
-		"           \"secret\": \"%s\",\n"
-		"           \"event\":  \"begin_backup\"\n"
-		"         }'
\n" - "
\n" - "
\n" - "

End backup

\n" - "
curl -X POST http://%s/api/report \\\n"
-		"     -H \"Content-Type: application/json\" \\\n"
-		"     -d '{\n"
-		"           \"client\": \"%s\",\n"
-		"           \"secret\": \"%s\",\n"
-		"           \"event\":  \"end_backup\"\n"
-		"         }'
\n" - "
\n" - "
\n" - "
\n", - client_result.secret, - request->host ? request->host : "localhost", - client_result.name, - client_result.secret, - request->host ? 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; - 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; - free(backups_result); - return buf; -} - -static char *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; -} - static enum MHD_Result create_post_collector(struct PostCollector *c) { c->data = malloc(512); c->size = 0; @@ -1438,7 +190,7 @@ static enum MHD_Result handler( if (!request->form.username_set || !request->form.password_set) { size_t html_len; - char *html = render_login_page("Invalid credentials", &html_len); + char *html = html_render_login_page("Invalid credentials", &html_len); if (!html) return MHD_NO; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); @@ -1451,7 +203,7 @@ static enum MHD_Result handler( if (!validate_credentials(server->app, request->form.username, request->form.password)) { size_t html_len; - char *html = render_login_page("Invalid credentials", &html_len); + char *html = html_render_login_page("Invalid credentials", &html_len); if (!html) return MHD_NO; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); @@ -1495,7 +247,7 @@ static enum MHD_Result handler( } struct CreateClientResult result; - ret = create_client(request, request->form.name, &result); + ret = create_client(server->app, request->form.name, &result); if(ret != 0) return MHD_NO; // Post-Redirect-Get: redirect back to GET /clients @@ -1524,7 +276,7 @@ static enum MHD_Result handler( return MHD_NO; } - ret = delete_client(request, request->form.client_id); + ret = delete_client(server->app, request->form.client_id); if(ret != 0) return MHD_NO; struct MHD_Response *response = MHD_create_response_from_buffer( @@ -1552,17 +304,17 @@ static enum MHD_Result handler( if(!cJSON_IsString(eventJson) || eventJson->valuestring == NULL) { abort(); } - enum ReportEvent event; + int is_finish; if(strcmp(eventJson->valuestring, "begin_backup") == 0) { - event = REV_BEGIN; + is_finish = 0; } else if(strcmp(eventJson->valuestring, "end_backup") == 0) { - event = REV_FINISH; + is_finish = 1; } else { abort(); } - ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, event); + ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, is_finish); if(ret == -2) { cJSON_Delete(rootJson); destroy_post_collector(&request->pp); @@ -1614,7 +366,7 @@ static enum MHD_Result handler( } struct CreateClientResult result; - ret = create_client(request, nameJson->valuestring, &result); + ret = create_client(server->app, nameJson->valuestring, &result); if(ret != 0) { // Should actually return an error page, but this will do for now return MHD_NO; @@ -1668,7 +420,7 @@ static enum MHD_Result handler( if(strcmp(url, "/login") == 0) { size_t html_len; - char *html = render_login_page(NULL, &html_len); + char *html = html_render_login_page(NULL, &html_len); if (!html) return MHD_NO; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); @@ -1688,8 +440,18 @@ static enum MHD_Result handler( return ret; } + // Fetch clients from app layer + struct ListClientResult *clients = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16); + ret = list_clients(server->app, clients); + if(ret != 0) { + free(clients); + return MHD_NO; + } + + // Render HTML size_t html_len; - char *html = render_clients_page(request, &html_len); + char *html = html_render_clients_page(request->host, clients, &html_len); + free(clients); if (!html) { return MHD_NO; @@ -1730,8 +492,34 @@ static enum MHD_Result handler( return ret; } + // Fetch client data from app layer + struct GetClientOnlyResult client_result; + ret = get_client(server->app, client_id, &client_result); + if(ret != 0) { + return MHD_NO; + } + + if(!client_result.found) { + // Not Found + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); + MHD_destroy_response(response); + return ret; + } + + struct GetBackupsResult *backups_result = malloc(sizeof(struct GetBackupsResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY); + backups_result->backups_len = MAX_BACKUPS_DISPLAY; + ret = get_client_backups(server->app, client_id, backups_result); + if(ret != 0) { + free(backups_result); + return MHD_NO; + } + + // Render HTML size_t html_len; - char *html = render_client_detail_page(request, client_id, &html_len); + char *html = html_render_client_detail_page(request->host, &client_result, backups_result, &html_len); + free(backups_result); if (!html) { // Not Found -- cgit v1.2.3