diff --git a/static/js/permissions.js b/static/js/permissions.js index 4575da1..b2af372 100644 --- a/static/js/permissions.js +++ b/static/js/permissions.js @@ -18,8 +18,12 @@ window.app = Vue.createApp({ showRevokeDialog: false, showGrantEquityDialog: false, showRevokeEquityDialog: false, + showBulkGrantDialog: false, + showBulkGrantErrors: false, permissionToRevoke: null, equityToRevoke: null, + bulkGranting: false, + bulkGrantResults: null, isSuperUser: false, grantForm: { user_id: '', @@ -32,6 +36,13 @@ window.app = Vue.createApp({ user_id: '', notes: '' }, + bulkGrantForm: { + user_ids: [], + account_id: '', + permission_type: 'read', + notes: '', + expires_at: '' + }, permissionTypeOptions: [ { value: 'read', @@ -77,6 +88,15 @@ window.app = Vue.createApp({ ) }, + isBulkGrantFormValid() { + return !!( + this.bulkGrantForm.user_ids && + this.bulkGrantForm.user_ids.length > 0 && + this.bulkGrantForm.account_id && + this.bulkGrantForm.permission_type + ) + }, + permissionsByUser() { const grouped = new Map() for (const perm of this.permissions) { @@ -296,6 +316,93 @@ window.app = Vue.createApp({ } }, + async bulkGrantPermissions() { + if (!this.isBulkGrantFormValid) { + this.$q.notify({ + type: 'warning', + message: 'Please fill in all required fields', + timeout: 3000 + }) + return + } + + this.bulkGranting = true + this.bulkGrantResults = null + + try { + const payload = { + user_ids: this.bulkGrantForm.user_ids, + account_id: this.bulkGrantForm.account_id, + permission_type: this.bulkGrantForm.permission_type + } + + if (this.bulkGrantForm.notes) { + payload.notes = this.bulkGrantForm.notes + } + + if (this.bulkGrantForm.expires_at) { + payload.expires_at = new Date(this.bulkGrantForm.expires_at).toISOString() + } + + const response = await LNbits.api.request( + 'POST', + '/castle/api/v1/admin/permissions/bulk-grant', + this.g.user.wallets[0].adminkey, + payload + ) + + this.bulkGrantResults = response.data + + // Show success notification + const message = this.bulkGrantResults.failure_count > 0 + ? `Bulk grant completed: ${this.bulkGrantResults.success_count} succeeded, ${this.bulkGrantResults.failure_count} failed` + : `Successfully granted permissions to ${this.bulkGrantResults.success_count} users` + + this.$q.notify({ + type: this.bulkGrantResults.failure_count > 0 ? 'warning' : 'positive', + message: message, + timeout: 5000 + }) + + // Reload permissions to show new grants + await this.loadPermissions() + + // Don't close dialog immediately if there were failures + // (so user can review errors) + if (this.bulkGrantResults.failure_count === 0) { + setTimeout(() => { + this.closeBulkGrantDialog() + }, 2000) + } + } catch (error) { + console.error('Failed to bulk grant permissions:', error) + this.$q.notify({ + type: 'negative', + message: 'Failed to bulk grant permissions', + caption: error.message || 'Unknown error', + timeout: 5000 + }) + } finally { + this.bulkGranting = false + } + }, + + closeBulkGrantDialog() { + this.showBulkGrantDialog = false + this.resetBulkGrantForm() + this.bulkGrantResults = null + }, + + resetBulkGrantForm() { + this.bulkGrantForm = { + user_ids: [], + account_id: '', + permission_type: 'read', + notes: '', + expires_at: '' + } + }, + getAccountName(accountId) { const account = this.accounts.find(a => a.id === accountId) return account ? account.name : accountId diff --git a/templates/castle/permissions.html b/templates/castle/permissions.html index 12385c8..91fa6f2 100644 --- a/templates/castle/permissions.html +++ b/templates/castle/permissions.html @@ -18,15 +18,36 @@

Manage user access to expense accounts

- - Admin access required - + + + Admin access required + + + + + + + + + + Bulk Grant + Grant to multiple users + + + + + +
@@ -368,6 +389,199 @@ + + + + +
Bulk Grant Permissions
+
+ Grant the same permission to multiple users at once. This saves time when onboarding multiple users to the same account. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Preview: This will grant {% raw %}{{ getPermissionLabel(bulkGrantForm.permission_type) }}{% endraw %} + permission to {% raw %}{{ bulkGrantForm.user_ids.length }}{% endraw %} user(s) + on account {% raw %}{{ getAccountName(bulkGrantForm.account_id) }}{% endraw %} +
+
+ + + + +
+ Results:
+ ✅ {% raw %}{{ bulkGrantResults.success_count }}{% endraw %} permissions granted successfully
+ + ❌ {% raw %}{{ bulkGrantResults.failure_count }}{% endraw %} failed + +
+ +
+
+ + + + + +
+
+ + + + + +
Bulk Grant Errors
+
+ + + + + + + + + {% raw %}{{ failure.user_id }}{% endraw %} + {% raw %}{{ failure.error }}{% endraw %} + + + + + + + + +
+
+