From d6a1c6e5b30628f1ec82882b0f483287aad86e7d Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 7 Nov 2025 23:06:24 +0100 Subject: [PATCH] Enables user selection for permissions Replaces the user ID input field with a user selection dropdown, allowing administrators to search and select users for permission management. This simplifies the process of assigning permissions and improves user experience. Fetches Castle users via a new API endpoint and filters them based on search input. Only users with Castle accounts (receivables, payables, equity, or permissions) are listed. --- static/js/permissions.js | 58 ++++++++++++++++++++++++++- templates/castle/permissions.html | 26 ++++++++++--- views_api.py | 65 +++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/static/js/permissions.js b/static/js/permissions.js index 43659ee..f74aa2b 100644 --- a/static/js/permissions.js +++ b/static/js/permissions.js @@ -5,6 +5,8 @@ window.app = Vue.createApp({ return { permissions: [], accounts: [], + users: [], + filteredUsers: [], loading: false, granting: false, revoking: false, @@ -48,6 +50,15 @@ window.app = Vue.createApp({ })) }, + userOptions() { + const users = this.filteredUsers.length > 0 ? this.filteredUsers : this.users + return users.map(user => ({ + id: user.id, + username: user.username || user.id, + label: user.username ? `${user.username} (${user.id.substring(0, 8)}...)` : user.id + })) + }, + isGrantFormValid() { return !!( this.grantForm.user_id && @@ -130,6 +141,48 @@ window.app = Vue.createApp({ } }, + async loadUsers() { + if (!this.isSuperUser) { + return + } + + try { + const response = await LNbits.api.request( + 'GET', + '/castle/api/v1/admin/castle-users', + this.g.user.wallets[0].adminkey + ) + this.users = response.data || [] + this.filteredUsers = [] + } catch (error) { + console.error('Failed to load users:', error) + this.$q.notify({ + type: 'negative', + message: 'Failed to load users', + caption: error.message || 'Unknown error', + timeout: 5000 + }) + } + }, + + filterUsers(val, update) { + if (val === '') { + update(() => { + this.filteredUsers = [] + }) + return + } + + update(() => { + const needle = val.toLowerCase() + this.filteredUsers = this.users.filter(user => { + const username = user.username || '' + const userId = user.id || '' + return username.toLowerCase().includes(needle) || userId.toLowerCase().includes(needle) + }) + }) + }, + async grantPermission() { if (!this.isGrantFormValid) { this.$q.notify({ @@ -283,7 +336,10 @@ window.app = Vue.createApp({ if (this.g.user.wallets && this.g.user.wallets.length > 0) { await this.loadAccounts() if (this.isSuperUser) { - await this.loadPermissions() + await Promise.all([ + this.loadPermissions(), + this.loadUsers() + ]) } } } diff --git a/templates/castle/permissions.html b/templates/castle/permissions.html index 85a7214..fc03376 100644 --- a/templates/castle/permissions.html +++ b/templates/castle/permissions.html @@ -187,19 +187,33 @@ - - + - + + list[dict]: + """ + Get all users who have Castle accounts (receivables, payables, equity, or permissions). + This returns only users who are actively using Castle, not all LNbits users. + Admin only. + """ + from lnbits.core.crud.users import get_user + from .crud import get_all_equity_eligible_users + + # Get all user-specific accounts (Receivable/Payable/Equity) + all_accounts = await get_all_accounts() + user_accounts = [acc for acc in all_accounts if acc.user_id is not None] + + # Get all users who have permissions + all_permissions = [] + for account in all_accounts: + account_perms = await get_account_permissions(account.id) + all_permissions.extend(account_perms) + + # Get all equity-eligible users + equity_users = await get_all_equity_eligible_users() + + # Collect unique user IDs + user_ids = set() + + # Add users with accounts + for acc in user_accounts: + user_ids.add(acc.user_id) + + # Add users with permissions + for perm in all_permissions: + user_ids.add(perm.user_id) + + # Add equity-eligible users + for equity in equity_users: + user_ids.add(equity.user_id) + + # Build user list with enriched data + users = [] + for user_id in user_ids: + # Get user details from core + user = await get_user(user_id) + + # Use username if available, otherwise use user_id + username = user.username if user and user.username else None + + # Get user's wallet setting if exists + user_wallet = await get_user_wallet(user_id) + + users.append({ + "id": user_id, + "user_id": user_id, # Compatibility with existing code + "username": username, + "user_wallet_id": user_wallet.user_wallet_id if user_wallet else None, + }) + + # Sort by username (None values last) + users.sort(key=lambda x: (x["username"] is None, x["username"] or "", x["user_id"])) + + return users + + @castle_api_router.get("/api/v1/user/wallet") async def api_get_user_wallet( user: User = Depends(check_user_exists),