Add NocoDB configuration tab to admin settings for persistent database connectivity
Build And Push Image / docker (push) Successful in 2m12s Details

This commit is contained in:
Matt 2025-09-04 13:36:03 +02:00
parent 3b455a3989
commit f84adeff21
4 changed files with 285 additions and 4 deletions

View File

@ -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": []

View File

@ -118,14 +118,14 @@
</v-card-text>
<!-- Action Buttons -->
<v-card-actions class="pa-4 pt-0">
<v-card-actions class="pa-4 pt-0 d-flex justify-center">
<v-btn
color="success"
variant="elevated"
size="small"
height="44"
:loading="loading"
@click="showPaymentDateDialog = true"
block
class="px-4"
>
<v-icon start size="16">mdi-check-circle</v-icon>
Mark as Paid

View File

@ -19,6 +19,10 @@
<v-icon start>mdi-email</v-icon>
Email
</v-tab>
<v-tab value="nocodb">
<v-icon start>mdi-database</v-icon>
NocoDB
</v-tab>
</v-tabs>
<v-window v-model="activeTab">
@ -327,6 +331,178 @@
</v-row>
</v-card-text>
</v-window-item>
<!-- NocoDB Settings -->
<v-window-item value="nocodb">
<v-card-text>
<!-- Edit Mode Toggle -->
<v-row class="mb-4">
<v-col>
<v-alert
v-if="!nocodbEditMode"
type="info"
variant="tonal"
density="compact"
>
<template v-slot:text>
Click "Edit NocoDB Configuration" to modify database settings
</template>
</v-alert>
<v-alert
v-if="nocodbEditMode"
type="warning"
variant="tonal"
density="compact"
>
<template v-slot:text>
NocoDB configuration is required for member management functionality
</template>
</v-alert>
</v-col>
<v-col cols="auto">
<v-btn
v-if="!nocodbEditMode"
color="primary"
variant="outlined"
@click="nocodbEditMode = true"
>
<v-icon start>mdi-pencil</v-icon>
Edit NocoDB Configuration
</v-btn>
<v-btn-group v-else>
<v-btn
color="success"
variant="flat"
@click="saveNocodbSettings"
>
<v-icon start>mdi-check</v-icon>
Save
</v-btn>
<v-btn
color="warning"
variant="outlined"
@click="testNocodbConnection"
:loading="testingNocodb"
>
<v-icon start>mdi-connection</v-icon>
Test Connection
</v-btn>
<v-btn
color="error"
variant="outlined"
@click="cancelNocodbEdit"
>
<v-icon start>mdi-close</v-icon>
Cancel
</v-btn>
</v-btn-group>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<h3 class="text-h6 mb-4">NocoDB Database Configuration</h3>
</v-col>
<v-col cols="12">
<v-text-field
v-model="settings.nocodb.url"
label="NocoDB URL"
variant="outlined"
placeholder="https://your-nocodb-instance.com"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="off"
:class="{ 'readonly-field': !nocodbEditMode }"
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="settings.nocodb.apiKey"
label="API Key"
variant="outlined"
:type="showNocodbApiKey ? 'text' : 'password'"
placeholder="Enter your NocoDB API token"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="new-password"
:class="{ 'readonly-field': !nocodbEditMode }"
>
<template v-slot:append-inner>
<v-icon
v-if="nocodbEditMode"
@click="showNocodbApiKey = !showNocodbApiKey"
:icon="showNocodbApiKey ? 'mdi-eye-off' : 'mdi-eye'"
class="cursor-pointer"
/>
</template>
</v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="settings.nocodb.baseId"
label="Base ID"
variant="outlined"
placeholder="Your NocoDB base ID"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="off"
:class="{ 'readonly-field': !nocodbEditMode }"
/>
</v-col>
<v-col cols="12">
<v-divider class="my-4" />
</v-col>
<v-col cols="12">
<h3 class="text-h6 mb-4">Table Mappings</h3>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="settings.nocodb.tables.members"
label="Members Table"
variant="outlined"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="off"
:class="{ 'readonly-field': !nocodbEditMode }"
/>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="settings.nocodb.tables.events"
label="Events Table"
variant="outlined"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="off"
:class="{ 'readonly-field': !nocodbEditMode }"
/>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="settings.nocodb.tables.rsvps"
label="RSVPs Table"
variant="outlined"
:readonly="!nocodbEditMode"
:disabled="!nocodbEditMode"
autocomplete="off"
:class="{ 'readonly-field': !nocodbEditMode }"
/>
</v-col>
</v-row>
<!-- Connection Status -->
<v-row v-if="nocodbConnectionStatus" class="mt-4">
<v-col>
<v-alert
:type="nocodbConnectionStatus.success ? 'success' : 'error'"
variant="tonal"
>
{{ nocodbConnectionStatus.message }}
</v-alert>
</v-col>
</v-row>
</v-card-text>
</v-window-item>
</v-window>
</v-card>
@ -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