Add Keycloak group management for member portal access control
All checks were successful
Build And Push Image / docker (push) Successful in 3m53s
All checks were successful
Build And Push Image / docker (push) Successful in 3m53s
- Add portal access control section to EditMemberDialog for admins - Implement API endpoints for managing member Keycloak groups - Add group selection UI with user/board/admin access levels - Enhance admin config with reload functionality - Support real-time group synchronization and status feedback
This commit is contained in:
@@ -172,6 +172,52 @@
|
||||
:error-messages="getFieldError('payment_due_date')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Portal Access Control Section (Admin Only) -->
|
||||
<template v-if="isAdmin && member?.keycloak_id">
|
||||
<v-col cols="12">
|
||||
<v-divider class="my-4" />
|
||||
<h3 class="text-h6 mb-4 text-primary">Portal Access Control</h3>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="form.portal_group"
|
||||
:items="portalGroupOptions"
|
||||
label="Portal Access Level"
|
||||
variant="outlined"
|
||||
hint="Controls user's access level in the portal"
|
||||
persistent-hint
|
||||
:loading="groupLoading"
|
||||
:disabled="groupLoading"
|
||||
:error="hasFieldError('portal_group')"
|
||||
:error-messages="getFieldError('portal_group')"
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<v-icon color="primary">mdi-shield-account</v-icon>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-alert
|
||||
v-if="groupSyncStatus"
|
||||
:type="groupSyncStatus.type"
|
||||
:text="groupSyncStatus.message"
|
||||
density="compact"
|
||||
class="mb-0"
|
||||
/>
|
||||
<v-chip
|
||||
v-else-if="member.keycloak_id"
|
||||
color="success"
|
||||
size="small"
|
||||
class="mt-2"
|
||||
>
|
||||
<v-icon start size="small">mdi-check-circle</v-icon>
|
||||
Portal Account Active
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -233,7 +279,8 @@ const form = ref({
|
||||
member_since: '',
|
||||
current_year_dues_paid: 'false',
|
||||
membership_date_paid: '',
|
||||
payment_due_date: ''
|
||||
payment_due_date: '',
|
||||
portal_group: 'user'
|
||||
});
|
||||
|
||||
// Additional form state
|
||||
@@ -243,6 +290,71 @@ const phoneData = ref(null);
|
||||
// Error handling
|
||||
const fieldErrors = ref<Record<string, string>>({});
|
||||
|
||||
// Auth state
|
||||
const { user, isAdmin } = useAuth();
|
||||
|
||||
// Portal group management
|
||||
const groupLoading = ref(false);
|
||||
const groupSyncStatus = ref<{ type: 'success' | 'warning' | 'error'; message: string } | null>(null);
|
||||
const originalPortalGroup = ref<string>('user');
|
||||
|
||||
const portalGroupOptions = [
|
||||
{ title: 'User - Basic Access', value: 'user' },
|
||||
{ title: 'Board Member - Extended Access', value: 'board' },
|
||||
{ title: 'Administrator - Full Access', value: 'admin' }
|
||||
];
|
||||
|
||||
// Watch for portal group changes and sync with Keycloak
|
||||
watch(() => form.value.portal_group, async (newGroup, oldGroup) => {
|
||||
if (!props.member?.keycloak_id || !isAdmin || newGroup === oldGroup || newGroup === originalPortalGroup.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[EditMemberDialog] Portal group changed:', oldGroup, '->', newGroup);
|
||||
|
||||
groupLoading.value = true;
|
||||
groupSyncStatus.value = null;
|
||||
|
||||
try {
|
||||
console.log('[EditMemberDialog] Updating Keycloak groups for member:', props.member.Id);
|
||||
|
||||
const response = await $fetch(`/api/members/${props.member.Id}/keycloak-groups`, {
|
||||
method: 'PUT',
|
||||
body: { newGroup }
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
groupSyncStatus.value = {
|
||||
type: 'success',
|
||||
message: `Successfully changed access level to ${newGroup}`
|
||||
};
|
||||
originalPortalGroup.value = newGroup; // Update original to prevent re-trigger
|
||||
console.log('[EditMemberDialog] Group change successful:', response.data);
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to update access level');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[EditMemberDialog] Failed to update Keycloak groups:', error);
|
||||
|
||||
groupSyncStatus.value = {
|
||||
type: 'error',
|
||||
message: error.data?.message || error.message || 'Failed to update access level'
|
||||
};
|
||||
|
||||
// Revert the form value on error
|
||||
form.value.portal_group = oldGroup || 'user';
|
||||
|
||||
} finally {
|
||||
groupLoading.value = false;
|
||||
|
||||
// Clear status after 5 seconds
|
||||
setTimeout(() => {
|
||||
groupSyncStatus.value = null;
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch dues paid switch
|
||||
watch(duesPaid, (newValue) => {
|
||||
form.value.current_year_dues_paid = newValue ? 'true' : 'false';
|
||||
@@ -334,7 +446,8 @@ const populateForm = () => {
|
||||
member_since: formatDateForInput(member.member_since || ''),
|
||||
current_year_dues_paid: member.current_year_dues_paid || 'false',
|
||||
membership_date_paid: formatDateForInput(member.membership_date_paid || ''),
|
||||
payment_due_date: formatDateForInput(member.payment_due_date || '')
|
||||
payment_due_date: formatDateForInput(member.payment_due_date || ''),
|
||||
portal_group: member.portal_group || 'user'
|
||||
};
|
||||
|
||||
// Set dues paid switch based on the string value
|
||||
|
||||
Reference in New Issue
Block a user