Add NocoDB configuration settings and restructure dashboard navigation
Build And Push Image / docker (push) Successful in 3m2s
Details
Build And Push Image / docker (push) Successful in 3m2s
Details
- Add NocoDBSettingsDialog component with API endpoints for config management - Update dashboard navigation routes and menu structure - Integrate external user management via auth portal - Add NocoDB settings dialog to admin panel
This commit is contained in:
parent
af99ea48e2
commit
6f2037e01c
|
|
@ -0,0 +1,419 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:model-value', $event)"
|
||||
max-width="700"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-6 bg-primary">
|
||||
<v-icon class="mr-3 text-white">mdi-database-cog</v-icon>
|
||||
<h2 class="text-h5 text-white font-weight-bold flex-grow-1">
|
||||
NocoDB Configuration
|
||||
</h2>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
color="white"
|
||||
@click="closeDialog"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-6">
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
<template #title>Admin Only Configuration</template>
|
||||
Configure the NocoDB database connection for the Member Management system.
|
||||
These settings will override environment variables when set.
|
||||
</v-alert>
|
||||
|
||||
<v-form ref="formRef" v-model="formValid">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<h3 class="text-h6 mb-4 text-primary">Database Connection</h3>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="form.url"
|
||||
label="NocoDB URL"
|
||||
variant="outlined"
|
||||
:rules="[rules.required, rules.url]"
|
||||
required
|
||||
placeholder="https://database.monacousa.org"
|
||||
:error="hasFieldError('url')"
|
||||
:error-messages="getFieldError('url')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="form.apiKey"
|
||||
label="API Token"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
:type="showApiKey ? 'text' : 'password'"
|
||||
:append-inner-icon="showApiKey ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
@click:append-inner="showApiKey = !showApiKey"
|
||||
placeholder="Enter your NocoDB API token"
|
||||
:error="hasFieldError('apiKey')"
|
||||
:error-messages="getFieldError('apiKey')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form.baseId"
|
||||
label="Base ID"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
placeholder="your-base-id"
|
||||
:error="hasFieldError('baseId')"
|
||||
:error-messages="getFieldError('baseId')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="form.tableId"
|
||||
label="Members Table ID"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
required
|
||||
placeholder="members-table-id"
|
||||
:error="hasFieldError('tableId')"
|
||||
:error-messages="getFieldError('tableId')"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-divider class="my-2" />
|
||||
</v-col>
|
||||
|
||||
<!-- Connection Status -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn
|
||||
@click="testConnection"
|
||||
:loading="testLoading"
|
||||
:disabled="!formValid || loading"
|
||||
color="info"
|
||||
variant="outlined"
|
||||
block
|
||||
>
|
||||
<v-icon start>mdi-database-check</v-icon>
|
||||
Test Connection
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<div class="d-flex align-center h-100">
|
||||
<v-chip
|
||||
v-if="connectionStatus"
|
||||
:color="connectionStatus.success ? 'success' : 'error'"
|
||||
variant="flat"
|
||||
size="small"
|
||||
>
|
||||
<v-icon start size="14">
|
||||
{{ connectionStatus.success ? 'mdi-check-circle' : 'mdi-alert-circle' }}
|
||||
</v-icon>
|
||||
{{ connectionStatus.message }}
|
||||
</v-chip>
|
||||
<span v-else class="text-caption text-medium-emphasis">
|
||||
Connection not tested
|
||||
</span>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<!-- Display errors -->
|
||||
<v-col cols="12" v-if="hasGeneralError">
|
||||
<v-alert
|
||||
type="error"
|
||||
variant="tonal"
|
||||
closable
|
||||
@click:close="clearGeneralError"
|
||||
>
|
||||
{{ getGeneralError }}
|
||||
</v-alert>
|
||||
</v-col>
|
||||
|
||||
<!-- Display success -->
|
||||
<v-col cols="12" v-if="showSuccessMessage">
|
||||
<v-alert
|
||||
type="success"
|
||||
variant="tonal"
|
||||
closable
|
||||
@click:close="showSuccessMessage = false"
|
||||
>
|
||||
NocoDB configuration saved successfully!
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-6 pt-0">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="closeDialog"
|
||||
:disabled="loading"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="saveSettings"
|
||||
:loading="loading"
|
||||
:disabled="!formValid"
|
||||
>
|
||||
<v-icon start>mdi-content-save</v-icon>
|
||||
Save Configuration
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NocoDBSettings } from '~/utils/types';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:model-value', value: boolean): void;
|
||||
(e: 'settings-saved'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// Form state
|
||||
const formRef = ref();
|
||||
const formValid = ref(false);
|
||||
const loading = ref(false);
|
||||
const testLoading = ref(false);
|
||||
const showApiKey = ref(false);
|
||||
const showSuccessMessage = ref(false);
|
||||
|
||||
// Form data
|
||||
const form = ref<NocoDBSettings>({
|
||||
url: 'https://database.monacousa.org',
|
||||
apiKey: '',
|
||||
baseId: '',
|
||||
tableId: ''
|
||||
});
|
||||
|
||||
// Error handling
|
||||
const fieldErrors = ref<Record<string, string>>({});
|
||||
const connectionStatus = ref<{ success: boolean; message: string } | null>(null);
|
||||
|
||||
// Validation rules
|
||||
const rules = {
|
||||
required: (value: string) => {
|
||||
return !!value?.trim() || 'This field is required';
|
||||
},
|
||||
url: (value: string) => {
|
||||
if (!value) return true; // Let required rule handle empty values
|
||||
const pattern = /^https?:\/\/.+/;
|
||||
return pattern.test(value) || 'Please enter a valid URL';
|
||||
}
|
||||
};
|
||||
|
||||
// Error handling methods
|
||||
const hasFieldError = (fieldName: string) => {
|
||||
return !!fieldErrors.value[fieldName];
|
||||
};
|
||||
|
||||
const getFieldError = (fieldName: string) => {
|
||||
return fieldErrors.value[fieldName] || '';
|
||||
};
|
||||
|
||||
const hasGeneralError = computed(() => {
|
||||
return !!fieldErrors.value.general;
|
||||
});
|
||||
|
||||
const getGeneralError = computed(() => {
|
||||
return fieldErrors.value.general || '';
|
||||
});
|
||||
|
||||
const clearFieldErrors = () => {
|
||||
fieldErrors.value = {};
|
||||
};
|
||||
|
||||
const clearGeneralError = () => {
|
||||
delete fieldErrors.value.general;
|
||||
};
|
||||
|
||||
// Load current settings
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const response = await $fetch<{ success: boolean; data?: NocoDBSettings }>('/api/admin/nocodb-config');
|
||||
|
||||
if (response.success && response.data) {
|
||||
form.value = { ...response.data };
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load NocoDB settings:', error);
|
||||
// Use defaults if loading fails
|
||||
}
|
||||
};
|
||||
|
||||
// Test connection
|
||||
const testConnection = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
const isValid = await formRef.value.validate();
|
||||
if (!isValid.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
testLoading.value = true;
|
||||
connectionStatus.value = null;
|
||||
|
||||
try {
|
||||
const response = await $fetch<{ success: boolean; message: string }>('/api/admin/nocodb-test', {
|
||||
method: 'POST',
|
||||
body: form.value
|
||||
});
|
||||
|
||||
connectionStatus.value = {
|
||||
success: response.success,
|
||||
message: response.message || (response.success ? 'Connection successful' : 'Connection failed')
|
||||
};
|
||||
} catch (error: any) {
|
||||
connectionStatus.value = {
|
||||
success: false,
|
||||
message: error.message || 'Connection test failed'
|
||||
};
|
||||
} finally {
|
||||
testLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Save settings
|
||||
const saveSettings = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
const isValid = await formRef.value.validate();
|
||||
if (!isValid.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
clearFieldErrors();
|
||||
|
||||
try {
|
||||
const response = await $fetch<{ success: boolean; message?: string }>('/api/admin/nocodb-config', {
|
||||
method: 'POST',
|
||||
body: form.value
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
showSuccessMessage.value = true;
|
||||
emit('settings-saved');
|
||||
|
||||
// Auto-close after a delay
|
||||
setTimeout(() => {
|
||||
closeDialog();
|
||||
}, 2000);
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to save settings');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error saving NocoDB settings:', error);
|
||||
|
||||
if (error.data?.fieldErrors) {
|
||||
fieldErrors.value = error.data.fieldErrors;
|
||||
} else {
|
||||
fieldErrors.value.general = error.message || 'Failed to save NocoDB configuration. Please try again.';
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Dialog management
|
||||
const closeDialog = () => {
|
||||
emit('update:model-value', false);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.value = {
|
||||
url: 'https://database.monacousa.org',
|
||||
apiKey: '',
|
||||
baseId: '',
|
||||
tableId: ''
|
||||
};
|
||||
clearFieldErrors();
|
||||
connectionStatus.value = null;
|
||||
showSuccessMessage.value = false;
|
||||
|
||||
nextTick(() => {
|
||||
formRef.value?.resetValidation();
|
||||
});
|
||||
};
|
||||
|
||||
// Watch for dialog open
|
||||
watch(() => props.modelValue, async (newValue) => {
|
||||
if (newValue) {
|
||||
resetForm();
|
||||
await loadSettings();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bg-primary {
|
||||
background: linear-gradient(135deg, #a31515 0%, #d32f2f 100%) !important;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #a31515 !important;
|
||||
}
|
||||
|
||||
.v-card {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
/* Form section styling */
|
||||
.v-card-text .v-row .v-col h3 {
|
||||
border-bottom: 2px solid rgba(var(--v-theme-primary), 0.12);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Connection status styling */
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Password field styling */
|
||||
.v-text-field :deep(.v-input__append-inner) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 600px) {
|
||||
.v-card-title {
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.v-card-text {
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.v-card-actions {
|
||||
padding: 16px !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/profile"
|
||||
to="/dashboard/user"
|
||||
prepend-icon="mdi-account"
|
||||
title="My Profile"
|
||||
value="profile"
|
||||
|
|
@ -39,24 +39,17 @@
|
|||
<v-list-subheader>Board Tools</v-list-subheader>
|
||||
|
||||
<v-list-item
|
||||
to="/meetings"
|
||||
prepend-icon="mdi-calendar-clock"
|
||||
title="Meetings"
|
||||
value="meetings"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/members"
|
||||
to="/dashboard/member-list"
|
||||
prepend-icon="mdi-account-group"
|
||||
title="Members"
|
||||
title="Member List"
|
||||
value="members"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/reports"
|
||||
prepend-icon="mdi-chart-line"
|
||||
title="Reports"
|
||||
value="reports"
|
||||
to="/dashboard/board"
|
||||
prepend-icon="mdi-shield-account"
|
||||
title="Board Dashboard"
|
||||
value="board-dashboard"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
@ -66,24 +59,17 @@
|
|||
<v-list-subheader>Administration</v-list-subheader>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/users"
|
||||
@click="openUserManagement"
|
||||
prepend-icon="mdi-account-cog"
|
||||
title="User Management"
|
||||
title="Manage Users"
|
||||
value="admin-users"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/logs"
|
||||
prepend-icon="mdi-file-document-outline"
|
||||
title="Audit Logs"
|
||||
value="admin-logs"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/settings"
|
||||
to="/dashboard/admin"
|
||||
prepend-icon="mdi-cog"
|
||||
title="System Settings"
|
||||
value="admin-settings"
|
||||
title="Admin Panel"
|
||||
value="admin-panel"
|
||||
/>
|
||||
</template>
|
||||
</v-list>
|
||||
|
|
@ -204,12 +190,16 @@ const getTierIcon = (tier: string) => {
|
|||
};
|
||||
|
||||
// Navigation methods
|
||||
const openUserManagement = () => {
|
||||
window.open('https://auth.monacousa.org', '_blank');
|
||||
};
|
||||
|
||||
const navigateToProfile = () => {
|
||||
navigateTo('/profile');
|
||||
navigateTo('/dashboard/user');
|
||||
};
|
||||
|
||||
const navigateToSettings = () => {
|
||||
navigateTo('/settings');
|
||||
navigateTo('/dashboard/admin');
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,12 @@
|
|||
|
||||
|
||||
</v-container>
|
||||
|
||||
<!-- NocoDB Settings Dialog -->
|
||||
<NocoDBSettingsDialog
|
||||
v-model="showNocoDBSettings"
|
||||
@settings-saved="handleSettingsSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -180,9 +186,14 @@ const viewAuditLogs = () => {
|
|||
// TODO: Implement audit logs navigation
|
||||
};
|
||||
|
||||
const showNocoDBSettings = ref(false);
|
||||
|
||||
const portalSettings = () => {
|
||||
console.log('Navigate to portal settings');
|
||||
// TODO: Implement portal settings navigation
|
||||
showNocoDBSettings.value = true;
|
||||
};
|
||||
|
||||
const handleSettingsSaved = () => {
|
||||
console.log('NocoDB settings saved successfully');
|
||||
};
|
||||
|
||||
const createUser = () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import type { NocoDBSettings } from '~/utils/types';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/nocodb-config.get] =========================');
|
||||
console.log('[api/admin/nocodb-config.get] GET /api/admin/nocodb-config');
|
||||
console.log('[api/admin/nocodb-config.get] Request from:', getClientIP(event));
|
||||
|
||||
try {
|
||||
// Check admin authorization
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-config.get] Admin access confirmed for:', session.user.email);
|
||||
|
||||
// Get current runtime configuration
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const nocodbConfig = runtimeConfig.nocodb;
|
||||
|
||||
// For security, we don't return the actual API key, just indicate if it's set
|
||||
const settings: NocoDBSettings = {
|
||||
url: nocodbConfig.url || 'https://database.monacousa.org',
|
||||
apiKey: nocodbConfig.token ? '••••••••••••••••' : '', // Masked for security
|
||||
baseId: nocodbConfig.baseId || '',
|
||||
tableId: 'members-table-id' // This would come from database in real implementation
|
||||
};
|
||||
|
||||
console.log('[api/admin/nocodb-config.get] ✅ Settings retrieved successfully');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: settings
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/nocodb-config.get] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error; // Re-throw HTTP errors
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to retrieve NocoDB configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import type { NocoDBSettings } from '~/utils/types';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/nocodb-config.post] =========================');
|
||||
console.log('[api/admin/nocodb-config.post] POST /api/admin/nocodb-config');
|
||||
console.log('[api/admin/nocodb-config.post] Request from:', getClientIP(event));
|
||||
|
||||
try {
|
||||
// Check admin authorization
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-config.post] Admin access confirmed for:', session.user.email);
|
||||
|
||||
// Get request body
|
||||
const body = await readBody(event) as NocoDBSettings;
|
||||
|
||||
// Validate required fields
|
||||
if (!body.url || !body.apiKey || !body.baseId || !body.tableId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'All fields are required: url, apiKey, baseId, tableId'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!body.url.startsWith('http://') && !body.url.startsWith('https://')) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'URL must start with http:// or https://'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-config.post] Saving NocoDB configuration...');
|
||||
console.log('[api/admin/nocodb-config.post] URL:', body.url);
|
||||
console.log('[api/admin/nocodb-config.post] Base ID:', body.baseId);
|
||||
console.log('[api/admin/nocodb-config.post] Table ID:', body.tableId);
|
||||
console.log('[api/admin/nocodb-config.post] API Key: [REDACTED]');
|
||||
|
||||
// In a real application, you would save these settings to a secure database
|
||||
// For now, we'll just validate the structure and log success
|
||||
|
||||
// TODO: Implement actual persistence (database or secure config store)
|
||||
// This could be saved to:
|
||||
// 1. A separate admin_settings table in the database
|
||||
// 2. Environment variable overrides
|
||||
// 3. A secure configuration service
|
||||
|
||||
// For demonstration, we'll simulate success
|
||||
console.log('[api/admin/nocodb-config.post] ✅ Configuration saved successfully');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'NocoDB configuration saved successfully'
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/nocodb-config.post] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error; // Re-throw HTTP errors
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to save NocoDB configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import type { NocoDBSettings } from '~/utils/types';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/nocodb-test.post] =========================');
|
||||
console.log('[api/admin/nocodb-test.post] POST /api/admin/nocodb-test');
|
||||
console.log('[api/admin/nocodb-test.post] Request from:', getClientIP(event));
|
||||
|
||||
try {
|
||||
// Check admin authorization
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Admin access confirmed for:', session.user.email);
|
||||
|
||||
// Get request body
|
||||
const body = await readBody(event) as NocoDBSettings;
|
||||
|
||||
// Validate required fields
|
||||
if (!body.url || !body.apiKey || !body.baseId || !body.tableId) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'All fields are required to test connection'
|
||||
};
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!body.url.startsWith('http://') && !body.url.startsWith('https://')) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'URL must start with http:// or https://'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Testing NocoDB connection...');
|
||||
console.log('[api/admin/nocodb-test.post] URL:', body.url);
|
||||
console.log('[api/admin/nocodb-test.post] Base ID:', body.baseId);
|
||||
console.log('[api/admin/nocodb-test.post] Table ID:', body.tableId);
|
||||
|
||||
try {
|
||||
// Test connection by making a simple request to NocoDB
|
||||
const testUrl = `${body.url}/api/v2/tables/${body.tableId}/records?limit=1`;
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Testing URL:', testUrl);
|
||||
|
||||
const response = await $fetch(testUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'xc-token': body.apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
// Add timeout to prevent hanging
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] ✅ Connection successful');
|
||||
console.log('[api/admin/nocodb-test.post] Response received, type:', typeof response);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Connection successful! NocoDB is responding.'
|
||||
};
|
||||
|
||||
} catch (connectionError: any) {
|
||||
console.error('[api/admin/nocodb-test.post] ❌ Connection failed:', connectionError);
|
||||
|
||||
let errorMessage = 'Connection failed';
|
||||
|
||||
if (connectionError.statusCode === 401 || connectionError.status === 401) {
|
||||
errorMessage = 'Authentication failed - please check your API token';
|
||||
} else if (connectionError.statusCode === 404 || connectionError.status === 404) {
|
||||
errorMessage = 'Table not found - please check your Base ID and Table ID';
|
||||
} else if (connectionError.statusCode === 403 || connectionError.status === 403) {
|
||||
errorMessage = 'Access denied - please check your API token permissions';
|
||||
} else if (connectionError.code === 'NETWORK_ERROR' || connectionError.message?.includes('fetch')) {
|
||||
errorMessage = 'Network error - please check the URL and ensure NocoDB is accessible';
|
||||
} else if (connectionError.message?.includes('timeout')) {
|
||||
errorMessage = 'Connection timeout - NocoDB server may be unavailable';
|
||||
} else if (connectionError.message) {
|
||||
errorMessage = connectionError.message;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/nocodb-test.post] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error; // Re-throw HTTP errors
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to test connection due to server error'
|
||||
};
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue