From f84adeff21ae4ecb50a098e728f46c3cfd268f8d Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 4 Sep 2025 13:36:03 +0200 Subject: [PATCH] Add NocoDB configuration tab to admin settings for persistent database connectivity --- .claude/settings.local.json | 6 +- .../document_symbols_cache_v23-06-25.pkl | Bin 5150176 -> 5179588 bytes components/DuesActionCard.vue | 6 +- pages/admin/settings/index.vue | 277 ++++++++++++++++++ 4 files changed, 285 insertions(+), 4 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 28233f1..5b54abb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -61,7 +61,11 @@ "Read(/Z:\\Repos\\monacousa-portal\\assets\\scss/**)", "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin/**)", "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin/**)", - "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin/**)" + "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin/**)", + "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin\\dashboard/**)", + "Read(/Z:\\Repos\\monacousa-portal\\components/**)", + "Read(/Z:\\Repos\\monacousa-portal\\components/**)", + "Read(/Z:\\Repos\\monacousa-portal\\pages\\admin\\settings/**)" ], "deny": [], "ask": [] diff --git a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl index f1eb18dba498e32a4cc30e7fecae1df31a21c6e8..cf6051f0f9a6c5d09cacfba4ef18ebfb537ce4e3 100644 GIT binary patch delta 23673 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 delta 179 zcmV~$$uh!m007|ev;R|;>_l2*%`PEXveP*9D()S=L!*=DaBaqn>F7Z`itqdV?1sOu zpU~7J=+&p+fI&lsMPf$8C5##~ZbH(eDbr@mnlo>~qLd}e(pF?-<>aj@ShH@!rY+lw zcI?`-@4%rW$4X9=ojP-_;zHG>E7xw++`4n`!J{Y7Uc7edt#){ezCYUaMikzQ|2S?v A*Z=?k diff --git a/components/DuesActionCard.vue b/components/DuesActionCard.vue index f0e6c72..feb8e76 100644 --- a/components/DuesActionCard.vue +++ b/components/DuesActionCard.vue @@ -118,14 +118,14 @@ - + mdi-check-circle Mark as Paid diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index 496c711..a6dc763 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -19,6 +19,10 @@ mdi-email Email + + mdi-database + NocoDB + @@ -327,6 +331,178 @@ + + + + + + + + + + + + + + + + + mdi-pencil + Edit NocoDB Configuration + + + + mdi-check + Save + + + mdi-connection + Test Connection + + + mdi-close + Cancel + + + + + + + +

NocoDB Database Configuration

+
+ + + + + + + + + + + + + + + +

Table Mappings

+
+ + + + + + + + + +
+ + + + + + {{ nocodbConnectionStatus.message }} + + + +
+
@@ -359,8 +535,12 @@ definePageMeta({ const activeTab = ref('general'); const generalEditMode = ref(false); const emailEditMode = ref(false); +const nocodbEditMode = ref(false); const showPassword = ref(false); +const showNocodbApiKey = ref(false); const testingEmail = ref(false); +const testingNocodb = ref(false); +const nocodbConnectionStatus = ref<{ success: boolean; message: string } | null>(null); const snackbar = ref(false); const snackbarText = ref(''); const snackbarColor = ref('success'); @@ -386,6 +566,16 @@ const settings = ref({ useTLS: true, fromName: 'MonacoUSA', fromEmail: 'noreply@monacousa.org' + }, + nocodb: { + url: '', + apiKey: '', + baseId: '', + tables: { + members: 'Members', + events: 'Events', + rsvps: 'RSVPs' + } } }); @@ -413,6 +603,7 @@ const currencies = [ // Load settings on mount onMounted(async () => { await loadSettings(); + await loadNocodbSettings(); }); // Methods @@ -488,6 +679,84 @@ const showNotification = (text: string, color: string = 'success') => { snackbar.value = true; }; +const loadNocodbSettings = async () => { + try { + const response = await $fetch<{ success: boolean; data?: any }>('/api/admin/nocodb-config'); + if (response.success && response.data) { + settings.value.nocodb = { + url: response.data.url || '', + apiKey: response.data.apiKey || '', + baseId: response.data.baseId || '', + tables: response.data.tables || { + members: 'Members', + events: 'Events', + rsvps: 'RSVPs' + } + }; + } + } catch (error) { + console.error('Error loading NocoDB settings:', error); + showNotification('Failed to load NocoDB settings', 'error'); + } +}; + +const saveNocodbSettings = async () => { + try { + const response = await $fetch('/api/admin/nocodb-config', { + method: 'POST', + body: settings.value.nocodb + }); + nocodbEditMode.value = false; + showNocodbApiKey.value = false; + showNotification('NocoDB settings saved successfully', 'success'); + // Reload settings to ensure they're persistent + await loadNocodbSettings(); + } catch (error) { + console.error('Error saving NocoDB settings:', error); + showNotification('Failed to save NocoDB settings', 'error'); + } +}; + +const testNocodbConnection = async () => { + testingNocodb.value = true; + nocodbConnectionStatus.value = null; + + try { + const response = await $fetch<{ success: boolean; message: string }>('/api/admin/nocodb-test', { + method: 'POST', + body: settings.value.nocodb + }); + + nocodbConnectionStatus.value = { + success: response.success, + message: response.message + }; + + if (response.success) { + showNotification('NocoDB connection successful', 'success'); + } else { + showNotification(response.message, 'error'); + } + } catch (error: any) { + nocodbConnectionStatus.value = { + success: false, + message: error.data?.message || 'Failed to connect to NocoDB' + }; + showNotification('Connection test failed', 'error'); + } finally { + testingNocodb.value = false; + } +}; + +const cancelNocodbEdit = () => { + if (originalSettings.value) { + settings.value.nocodb = { ...originalSettings.value.nocodb }; + } + nocodbEditMode.value = false; + showNocodbApiKey.value = false; + nocodbConnectionStatus.value = null; +}; + // Watch for edit mode changes to backup original settings watch(generalEditMode, (newVal) => { if (newVal) { @@ -505,6 +774,14 @@ watch(emailEditMode, (newVal) => { } }); +watch(nocodbEditMode, (newVal) => { + if (newVal) { + originalSettings.value = { + nocodb: { ...settings.value.nocodb } + }; + } +}); + // Prevent browser autofill on mount onMounted(() => { // Disable autofill for all inputs initially