diff --git a/apps/admin.json b/apps/admin.json
index a468be8d..bad3f9b9 100644
--- a/apps/admin.json
+++ b/apps/admin.json
@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🎛",
- "previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
+ "previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256"
}
diff --git a/apps/admin/app.js b/apps/admin/app.js
index 5916f11f..5f142dda 100644
--- a/apps/admin/app.js
+++ b/apps/admin/app.js
@@ -8,12 +8,20 @@ tfrpc.register(function global_settings_set(key, value) {
return core.globalSettingsSet(key, value);
});
+tfrpc.register(function addBlock(id) {
+ return ssb.addBlock(id);
+});
+tfrpc.register(function removeBlock(id) {
+ return ssb.removeBlock(id);
+});
+
async function main() {
try {
let data = {
users: {},
granted: await core.allPermissionsGranted(),
settings: await core.globalSettingsDescriptions(),
+ blocks: await ssb.getBlocks(),
};
for (let user of await core.users()) {
data.users[user] = await core.permissionsForUser(user);
diff --git a/apps/admin/script.js b/apps/admin/script.js
index 4b97b542..485f933a 100644
--- a/apps/admin/script.js
+++ b/apps/admin/script.js
@@ -16,6 +16,14 @@ function delete_user(user) {
}
}
+async function add_block() {
+ await tfrpc.rpc.addBlock(document.getElementById('add_block').value);
+}
+
+async function remove_block(id) {
+ await tfrpc.rpc.removeBlock(id);
+}
+
function global_settings_set(key, value) {
tfrpc.rpc
.global_settings_set(key, value)
@@ -94,11 +102,32 @@ ${description.value} permission_template(x))}
`;
+ const block_template = (block) => html`
+
+
+ ${block.id}
+ ${new Date(block.timestamp)}
+
+ `;
const users_template = (users) =>
html`
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
`;
+ const blocks_template = (blocks) =>
+ html`
+
+
+
+
+
+ ${blocks.map((b) => block_template(b))}
+
`;
const page_template = (data) =>
html`
@@ -109,7 +138,7 @@ ${description.value} html`${input_template(x, data.settings[x])}`)}
- ${users_template(data.users)}
+ ${users_template(data.users)} ${blocks_template(data.blocks)}
`;
render(page_template(g_data), document.body);
});
diff --git a/core/core.js b/core/core.js
index da7e348c..9a73ba0b 100644
--- a/core/core.js
+++ b/core/core.js
@@ -515,6 +515,7 @@ async function getProcessBlob(blobId, key, options) {
await imports.core.permissionTest('modify_blocks', `Unblock ${id}.`);
await ssb_internal.removeBlock(id);
};
+ imports.ssb.getBlocks = ssb_internal.getBlocks.bind(ssb_internal);
}
if (
diff --git a/src/ssb.db.c b/src/ssb.db.c
index d48962b2..961702f9 100644
--- a/src/ssb.db.c
+++ b/src/ssb.db.c
@@ -2936,3 +2936,15 @@ bool tf_ssb_db_is_blocked(sqlite3* db, const char* id)
}
return is_blocked;
}
+
+void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data)
+{
+ sqlite3_stmt* statement = NULL;
+ if (sqlite3_prepare_v2(db, "SELECT id, timestamp FROM blocks ORDER BY timestamp", -1, &statement, NULL) == SQLITE_OK)
+ {
+ while (sqlite3_step(statement) == SQLITE_ROW)
+ {
+ callback((const char*)sqlite3_column_text(statement, 0), sqlite3_column_double(statement, 1), user_data);
+ }
+ }
+}
diff --git a/src/ssb.db.h b/src/ssb.db.h
index dfde0790..6f87c440 100644
--- a/src/ssb.db.h
+++ b/src/ssb.db.h
@@ -639,4 +639,12 @@ void tf_ssb_db_remove_block(sqlite3* db, const char* id);
*/
bool tf_ssb_db_is_blocked(sqlite3* db, const char* id);
+/**
+** Get block list.
+** @param db The database.
+** @param callback Called for each entry.
+** @param user_data User data to be passed to the callback.
+*/
+void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data);
+
/** @} */
diff --git a/src/ssb.js.c b/src/ssb.js.c
index db5d466c..6cb600f8 100644
--- a/src/ssb.js.c
+++ b/src/ssb.js.c
@@ -2232,6 +2232,68 @@ static JSValue _tf_ssb_remove_block(JSContext* context, JSValueConst this_val, i
return result;
}
+typedef struct _block_t
+{
+ char id[k_id_base64_len];
+ double timestamp;
+} block_t;
+
+typedef struct _get_blocks_t
+{
+ block_t* blocks;
+ int count;
+ JSValue promise[2];
+} get_blocks_t;
+
+static void _get_blocks_callback(const char* id, double timestamp, void* user_data)
+{
+ get_blocks_t* work = user_data;
+ work->blocks = tf_resize_vec(work->blocks, sizeof(block_t) * (work->count + 1));
+ work->blocks[work->count] = (block_t) { .timestamp = timestamp };
+ tf_string_set(work->blocks[work->count].id, sizeof(work->blocks[work->count].id), id);
+ work->count++;
+}
+
+static void _tf_ssb_get_blocks_work(tf_ssb_t* ssb, void* user_data)
+{
+ sqlite3* db = tf_ssb_acquire_db_reader(ssb);
+ tf_ssb_db_get_blocks(db, _get_blocks_callback, user_data);
+ tf_ssb_release_db_reader(ssb, db);
+}
+
+static void _tf_ssb_get_blocks_after_work(tf_ssb_t* ssb, int status, void* user_data)
+{
+ get_blocks_t* work = user_data;
+ JSContext* context = tf_ssb_get_context(ssb);
+
+ JSValue result = JS_NewArray(context);
+ for (int i = 0; i < work->count; i++)
+ {
+ JSValue entry = JS_NewObject(context);
+ JS_SetPropertyStr(context, entry, "id", JS_NewString(context, work->blocks[i].id));
+ JS_SetPropertyStr(context, entry, "timestamp", JS_NewFloat64(context, work->blocks[i].timestamp));
+ JS_SetPropertyUint32(context, result, i, entry);
+ }
+
+ JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
+ JS_FreeValue(context, result);
+ tf_util_report_error(context, error);
+ JS_FreeValue(context, error);
+ JS_FreeValue(context, work->promise[0]);
+ JS_FreeValue(context, work->promise[1]);
+ tf_free(work);
+}
+
+static JSValue _tf_ssb_get_blocks(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
+{
+ tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
+ get_blocks_t* work = tf_malloc(sizeof(get_blocks_t));
+ *work = (get_blocks_t) { 0 };
+ JSValue result = JS_NewPromiseCapability(context, work->promise);
+ tf_ssb_run_work(ssb, _tf_ssb_get_blocks_work, _tf_ssb_get_blocks_after_work, work);
+ return result;
+}
+
void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
{
JS_NewClassID(&_tf_ssb_classId);
@@ -2289,6 +2351,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
JS_SetPropertyStr(context, object_internal, "addBlock", JS_NewCFunction(context, _tf_ssb_add_block, "addBlock", 1));
JS_SetPropertyStr(context, object_internal, "removeBlock", JS_NewCFunction(context, _tf_ssb_remove_block, "removeBlock", 1));
+ JS_SetPropertyStr(context, object_internal, "getBlocks", JS_NewCFunction(context, _tf_ssb_get_blocks, "getBlocks", 0));
JS_FreeValue(context, global);
}