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.
This commit is contained in:
parent
fc12dae435
commit
d6a1c6e5b3
3 changed files with 142 additions and 7 deletions
|
|
@ -5,6 +5,8 @@ window.app = Vue.createApp({
|
||||||
return {
|
return {
|
||||||
permissions: [],
|
permissions: [],
|
||||||
accounts: [],
|
accounts: [],
|
||||||
|
users: [],
|
||||||
|
filteredUsers: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
granting: false,
|
granting: false,
|
||||||
revoking: 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() {
|
isGrantFormValid() {
|
||||||
return !!(
|
return !!(
|
||||||
this.grantForm.user_id &&
|
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() {
|
async grantPermission() {
|
||||||
if (!this.isGrantFormValid) {
|
if (!this.isGrantFormValid) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
|
|
@ -283,7 +336,10 @@ window.app = Vue.createApp({
|
||||||
if (this.g.user.wallets && this.g.user.wallets.length > 0) {
|
if (this.g.user.wallets && this.g.user.wallets.length > 0) {
|
||||||
await this.loadAccounts()
|
await this.loadAccounts()
|
||||||
if (this.isSuperUser) {
|
if (this.isSuperUser) {
|
||||||
await this.loadPermissions()
|
await Promise.all([
|
||||||
|
this.loadPermissions(),
|
||||||
|
this.loadUsers()
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,19 +187,33 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="q-gutter-md">
|
<q-card-section class="q-gutter-md">
|
||||||
<!-- User ID -->
|
<!-- User -->
|
||||||
<q-input
|
<q-select
|
||||||
v-model="grantForm.user_id"
|
v-model="grantForm.user_id"
|
||||||
label="User ID *"
|
label="User *"
|
||||||
hint="Wallet ID of the user"
|
hint="Search and select a user"
|
||||||
|
:options="userOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="label"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-input
|
||||||
|
@filter="filterUsers"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
:rules="[val => !!val || 'User ID is required']"
|
:rules="[val => !!val || 'User is required']"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="person"></q-icon>
|
<q-icon name="person"></q-icon>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
<template v-slot:no-option>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section class="text-grey">
|
||||||
|
No users found
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
<!-- Account -->
|
<!-- Account -->
|
||||||
<q-select
|
<q-select
|
||||||
|
|
|
||||||
65
views_api.py
65
views_api.py
|
|
@ -1339,6 +1339,71 @@ async def api_get_all_users(
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
@castle_api_router.get("/api/v1/admin/castle-users")
|
||||||
|
async def api_get_castle_users(
|
||||||
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
|
) -> 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")
|
@castle_api_router.get("/api/v1/user/wallet")
|
||||||
async def api_get_user_wallet(
|
async def api_get_user_wallet(
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue