window.app = Vue.createApp({ el: '#vue', mixins: [windowMixin], data() { return { permissions: [], accounts: [], users: [], filteredUsers: [], loading: false, granting: false, revoking: false, activeTab: 'by-user', showGrantDialog: false, showRevokeDialog: false, permissionToRevoke: null, isSuperUser: false, grantForm: { user_id: '', account_id: '', permission_type: 'read', notes: '', expires_at: '' }, permissionTypeOptions: [ { value: 'read', label: 'Read', description: 'View account and balance' }, { value: 'submit_expense', label: 'Submit Expense', description: 'Submit expenses to this account' }, { value: 'manage', label: 'Manage', description: 'Full account management' } ] } }, computed: { accountOptions() { return this.accounts.map(acc => ({ id: acc.id, name: acc.name })) }, 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 && this.grantForm.account_id && this.grantForm.permission_type ) }, permissionsByUser() { const grouped = new Map() for (const perm of this.permissions) { if (!grouped.has(perm.user_id)) { grouped.set(perm.user_id, []) } grouped.get(perm.user_id).push(perm) } return grouped }, permissionsByAccount() { const grouped = new Map() for (const perm of this.permissions) { if (!grouped.has(perm.account_id)) { grouped.set(perm.account_id, []) } grouped.get(perm.account_id).push(perm) } return grouped } }, methods: { async loadPermissions() { if (!this.isSuperUser) { this.$q.notify({ type: 'warning', message: 'Admin access required to view permissions', timeout: 3000 }) return } this.loading = true try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/admin/permissions', this.g.user.wallets[0].adminkey ) this.permissions = response.data } catch (error) { console.error('Failed to load permissions:', error) this.$q.notify({ type: 'negative', message: 'Failed to load permissions', caption: error.message || 'Unknown error', timeout: 5000 }) } finally { this.loading = false } }, async loadAccounts() { try { const response = await LNbits.api.request( 'GET', '/castle/api/v1/accounts', this.g.user.wallets[0].inkey ) this.accounts = response.data } catch (error) { console.error('Failed to load accounts:', error) this.$q.notify({ type: 'negative', message: 'Failed to load accounts', caption: error.message || 'Unknown error', timeout: 5000 }) } }, 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({ type: 'warning', message: 'Please fill in all required fields', timeout: 3000 }) return } this.granting = true try { const payload = { user_id: this.grantForm.user_id, account_id: this.grantForm.account_id, permission_type: this.grantForm.permission_type } if (this.grantForm.notes) { payload.notes = this.grantForm.notes } if (this.grantForm.expires_at) { payload.expires_at = new Date(this.grantForm.expires_at).toISOString() } await LNbits.api.request( 'POST', '/castle/api/v1/admin/permissions', this.g.user.wallets[0].adminkey, payload ) this.$q.notify({ type: 'positive', message: 'Permission granted successfully', timeout: 3000 }) this.showGrantDialog = false this.resetGrantForm() await this.loadPermissions() } catch (error) { console.error('Failed to grant permission:', error) this.$q.notify({ type: 'negative', message: 'Failed to grant permission', caption: error.message || 'Unknown error', timeout: 5000 }) } finally { this.granting = false } }, confirmRevokePermission(permission) { this.permissionToRevoke = permission this.showRevokeDialog = true }, async revokePermission() { if (!this.permissionToRevoke) return this.revoking = true try { await LNbits.api.request( 'DELETE', `/castle/api/v1/admin/permissions/${this.permissionToRevoke.id}`, this.g.user.wallets[0].adminkey ) this.$q.notify({ type: 'positive', message: 'Permission revoked successfully', timeout: 3000 }) this.showRevokeDialog = false this.permissionToRevoke = null await this.loadPermissions() } catch (error) { console.error('Failed to revoke permission:', error) this.$q.notify({ type: 'negative', message: 'Failed to revoke permission', caption: error.message || 'Unknown error', timeout: 5000 }) } finally { this.revoking = false } }, resetGrantForm() { this.grantForm = { user_id: '', account_id: '', permission_type: 'read', notes: '', expires_at: '' } }, getAccountName(accountId) { const account = this.accounts.find(a => a.id === accountId) return account ? account.name : accountId }, getPermissionLabel(permissionType) { const option = this.permissionTypeOptions.find(opt => opt.value === permissionType) return option ? option.label : permissionType }, getPermissionColor(permissionType) { switch (permissionType) { case 'read': return 'blue' case 'submit_expense': return 'green' case 'manage': return 'red' default: return 'grey' } }, getPermissionIcon(permissionType) { switch (permissionType) { case 'read': return 'visibility' case 'submit_expense': return 'add_circle' case 'manage': return 'admin_panel_settings' default: return 'security' } }, formatDate(dateString) { if (!dateString) return '-' const date = new Date(dateString) return date.toLocaleString() } }, async created() { // Check if user is super user this.isSuperUser = this.g.user.super_user || false if (this.g.user.wallets && this.g.user.wallets.length > 0) { await this.loadAccounts() if (this.isSuperUser) { await Promise.all([ this.loadPermissions(), this.loadUsers() ]) } } } }) window.app.mount('#vue')