From 58a763bcd18d0b52502b3fefc56016cb45324edb Mon Sep 17 00:00:00 2001 From: tamilselvanp57 Date: Wed, 22 Jul 2026 02:22:48 +0530 Subject: [PATCH] Add Dev Center UI for viewing app users (#3349) (#3417) Adds a 'View Users' link on the app Analytics tab that opens a modal listing users who have authenticated into the app (username + email, where the user has granted email:read to the app). Consumes the existing puter.apps.get(...).users()/.getUsers() client SDK method, backed by the app-telemetry driver's get_users - no new backend work needed, per discussion on the issue. Relates to #3349 --- src/dev-center/index.html | 24 ++++++++++++ src/dev-center/js/apps.js | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/src/dev-center/index.html b/src/dev-center/index.html index 5a3b7d569..9e5153593 100644 --- a/src/dev-center/index.html +++ b/src/dev-center/index.html @@ -275,6 +275,30 @@ class="button button-primary button-block disabled insta-deploy-existing-app-deploy-btn">Deploy
Cancel
+ + + + +

Users who have authenticated into

+
+ + + + + + + + +
UsernameEmail
+ +
+ +
+ + + Close +
+
diff --git a/src/dev-center/js/apps.js b/src/dev-center/js/apps.js index cef38e823..219cc793b 100644 --- a/src/dev-center/js/apps.js +++ b/src/dev-center/js/apps.js @@ -545,6 +545,7 @@ function generate_edit_app_section (app) {

Users

+
View Users

Opens

@@ -3030,6 +3031,82 @@ async function render_analytics (period) { puter.ui.hideSpinner(); } +// ── App Users modal ────────────────────────────────────────────── +let app_users_state = null; // { app, offset, pageSize, done } + +async function open_app_users_modal (app) { + app_users_state = { app, offset: 0, pageSize: 50, done: false }; + $('.app-users-modal-app-name').text(app.title || app.name); + $('#app-users-table > tbody').empty(); + $('.app-users-empty-notice').hide(); + $('.app-users-load-more-btn').hide(); + $('.app-users-modal').get(0).showModal(); + await load_more_app_users(); +} + +function render_app_user_row (user) { + const emailCell = Object.prototype.hasOwnProperty.call(user, 'user_email') + ? html_encode(user.user_email) + : 'Not shared'; + return ` + ${html_encode(user.user)} + ${emailCell} + `; +} + +async function load_more_app_users () { + if ( !app_users_state || app_users_state.done ) return; + + $('.app-users-loading-notice').show(); + $('.app-users-load-more-btn').hide(); + + try { + const app = await puter.apps.get(app_users_state.app.name); + const users = await app.getUsers({ + limit: app_users_state.pageSize, + offset: app_users_state.offset, + }); + + if ( !users || users.length === 0 ) { + app_users_state.done = true; + if ( app_users_state.offset === 0 ) { + $('.app-users-empty-notice').show(); + } + return; + } + + users.forEach(user => { + $('#app-users-table > tbody').append(render_app_user_row(user)); + }); + + app_users_state.offset += users.length; + if ( users.length < app_users_state.pageSize ) { + app_users_state.done = true; + } else { + $('.app-users-load-more-btn').show(); + } + } catch (e) { + console.error('Failed to load app users', e); + $('.app-users-empty-notice').text('Failed to load users.').show(); + } finally { + $('.app-users-loading-notice').hide(); + } +} + +$(document).on('click', '.view-app-users-btn', async function (e) { + if ( !currently_editing_app ) return; + await open_app_users_modal(currently_editing_app); +}); + +$(document).on('click', '.app-users-load-more-btn', async function (e) { + await load_more_app_users(); +}); + +$(document).on('click', '.app-users-cancel', function (e) { + $('.app-users-modal').get(0).close(); + app_users_state = null; +}); +// added the logic to open the analytics section when clicking on the stats cell in the app card $(document).on('click', '.stats-cell', function (e) { edit_app_section($(this).attr('data-app-name'), 'analytics');