ssb: The block list can be crudely managed through the admin app.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 8m54s

This commit is contained in:
2025-11-29 11:33:16 -05:00
parent fa4ef3b082
commit bc3fd57d7a
7 changed files with 123 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🎛", "emoji": "🎛",
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256" "previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256"
} }

View File

@@ -8,12 +8,20 @@ tfrpc.register(function global_settings_set(key, value) {
return core.globalSettingsSet(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() { async function main() {
try { try {
let data = { let data = {
users: {}, users: {},
granted: await core.allPermissionsGranted(), granted: await core.allPermissionsGranted(),
settings: await core.globalSettingsDescriptions(), settings: await core.globalSettingsDescriptions(),
blocks: await ssb.getBlocks(),
}; };
for (let user of await core.users()) { for (let user of await core.users()) {
data.users[user] = await core.permissionsForUser(user); data.users[user] = await core.permissionsForUser(user);

View File

@@ -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) { function global_settings_set(key, value) {
tfrpc.rpc tfrpc.rpc
.global_settings_set(key, value) .global_settings_set(key, value)
@@ -94,11 +102,32 @@ ${description.value}</textarea
${user}: ${permissions.map((x) => permission_template(x))} ${user}: ${permissions.map((x) => permission_template(x))}
</li> </li>
`; `;
const block_template = (block) => html`
<li class="w3-card w3-margin">
<button
class="w3-button w3-theme-action"
@click=${(e) => remove_block(block.id)}
>
Delete
</button>
<code>${block.id}</code>
${new Date(block.timestamp)}
</li>
`;
const users_template = (users) => const users_template = (users) =>
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header> html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
<ul class="w3-ul"> <ul class="w3-ul">
${Object.entries(users).map((u) => user_template(u[0], u[1]))} ${Object.entries(users).map((u) => user_template(u[0], u[1]))}
</ul>`; </ul>`;
const blocks_template = (blocks) =>
html` <header class="w3-container w3-theme-l2"><h2>Blocks</h2></header>
<div class="w3-row w3-margin">
<input type="text" class="w3-threequarter w3-input" id="add_block"></input>
<button class="w3-quarter w3-button w3-theme-action" @click=${add_block}>Add</button>
</div>
<ul class="w3-ul">
${blocks.map((b) => block_template(b))}
</ul>`;
const page_template = (data) => const page_template = (data) =>
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%"> html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header> <header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
@@ -109,7 +138,7 @@ ${description.value}</textarea
.map((x) => html`${input_template(x, data.settings[x])}`)} .map((x) => html`${input_template(x, data.settings[x])}`)}
</ul> </ul>
</div> </div>
${users_template(data.users)} ${users_template(data.users)} ${blocks_template(data.blocks)}
</div> `; </div> `;
render(page_template(g_data), document.body); render(page_template(g_data), document.body);
}); });

View File

@@ -515,6 +515,7 @@ async function getProcessBlob(blobId, key, options) {
await imports.core.permissionTest('modify_blocks', `Unblock ${id}.`); await imports.core.permissionTest('modify_blocks', `Unblock ${id}.`);
await ssb_internal.removeBlock(id); await ssb_internal.removeBlock(id);
}; };
imports.ssb.getBlocks = ssb_internal.getBlocks.bind(ssb_internal);
} }
if ( if (

View File

@@ -2936,3 +2936,15 @@ bool tf_ssb_db_is_blocked(sqlite3* db, const char* id)
} }
return is_blocked; 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);
}
}
}

View File

@@ -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); 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);
/** @} */ /** @} */

View File

@@ -2232,6 +2232,68 @@ static JSValue _tf_ssb_remove_block(JSContext* context, JSValueConst this_val, i
return result; 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) void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
{ {
JS_NewClassID(&_tf_ssb_classId); 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, "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, "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, "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); JS_FreeValue(context, global);
} }