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
This commit is contained in:
tamilselvanp57
2026-07-21 13:52:48 -07:00
committed by GitHub
parent fd91d72654
commit 58a763bcd1
2 changed files with 101 additions and 0 deletions
+24
View File
@@ -275,6 +275,30 @@
class="button button-primary button-block disabled insta-deploy-existing-app-deploy-btn">Deploy</button>
<div class="insta-deploy-cancel">Cancel</div>
</dialog>
<!---------------------------------------->
<!-- App Users Modal -->
<!---------------------------------------->
<dialog class="app-users-modal" style="width: 500px; max-width: 90vw;">
<p>Users who have authenticated into <strong class="app-users-modal-app-name"></strong></p>
<div class="app-users-list-container" style="max-height: 400px; overflow-y: auto; margin-top: 10px;">
<table class="table" id="app-users-table" style="width: 100%;">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
</tr>
</thead>
<tbody></tbody>
</table>
<p class="app-users-empty-notice" style="display:none; text-align:center; color:#888;">No users yet.</p>
</div>
</div>
<div style="margin-top: 15px; display: flex; justify-content: space-between; align-items: center;">
<span class="app-users-loading-notice" style="display:none; color:#888; font-size: 13px;">Loading…</span>
<button class="button app-users-load-more-btn" style="display:none;">Load more</button>
<span class="app-users-cancel" style="cursor:pointer; margin-left:auto;">Close</span>
</div>
</dialog>
<!---------------------------------------->
<!-- App List -->
+77
View File
@@ -545,6 +545,7 @@ function generate_edit_app_section (app) {
<div class="analytics-card" id="analytics-users">
<h3 style="margin-top:0;">Users</h3>
<div class="count" style="font-size: 35px;"></div>
<div class="view-app-users-btn" style="margin-top:10px; cursor:pointer; color:var(--primary-color, #3f51b5); font-size:13px;">View Users</div>
</div>
<div class="analytics-card" id="analytics-opens">
<h3 style="margin-top:0;">Opens</h3>
@@ -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)
: '<span style="color:#aaa;">Not shared</span>';
return `<tr>
<td>${html_encode(user.user)}</td>
<td>${emailCell}</td>
</tr>`;
}
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');