feat: agregar página de debug para Metabase
All checks were successful
build-and-deploy / build (push) Successful in 41s
build-and-deploy / deploy (push) Successful in 3s

- Crear componente MetabaseCardDisplay para mostrar detalles de queries
- Crear componente MetabaseCardsTable para listar todas las queries
- Crear página /metabase-debug con vistas de tabla, cards y queries Panorama
- Agregar API routes para cards de Metabase (GET, POST, export)
- Actualizar metabase.ts para soportar API Key authentication
- Agregar configuración de Metabase API Key en nuxt.config.ts
- Documentar todos los endpoints disponibles en METABASE_API_ENDPOINTS.md
This commit is contained in:
2025-10-14 01:34:56 -06:00
parent d0b0dc3c56
commit 90aebbde3d
10 changed files with 1069 additions and 8 deletions

View File

@@ -0,0 +1,24 @@
/**
* Get a specific Metabase card by ID
*/
export default defineEventHandler(async (event) => {
try {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'Card ID is required'
})
}
const card = await getMetabaseCard(parseInt(id))
return card
} catch (error: any) {
console.error('[API] Failed to get Metabase card:', error)
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Failed to fetch card'
})
}
})

View File

@@ -0,0 +1,24 @@
/**
* Execute a Metabase card query with cache (GET)
*/
export default defineEventHandler(async (event) => {
try {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'Card ID is required'
})
}
const result = await executeCardQueryCached(parseInt(id))
return result
} catch (error: any) {
console.error('[API] Failed to execute cached query:', error)
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Failed to execute cached query'
})
}
})

View File

@@ -0,0 +1,27 @@
/**
* Execute a Metabase card query (POST)
*/
export default defineEventHandler(async (event) => {
try {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'Card ID is required'
})
}
const body = await readBody(event)
const parameters = body?.parameters
const result = await executeCardQuery(parseInt(id), parameters)
return result
} catch (error: any) {
console.error('[API] Failed to execute Metabase card query:', error)
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Failed to execute query'
})
}
})

View File

@@ -0,0 +1,18 @@
/**
* Get all Metabase cards/questions
*/
export default defineEventHandler(async (event) => {
try {
const query = getQuery(event)
const filter = query.f as string | undefined
const cards = await getMetabaseCards(filter)
return cards
} catch (error: any) {
console.error('[API] Failed to get Metabase cards:', error)
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Failed to fetch cards'
})
}
})

View File

@@ -6,6 +6,7 @@
const config = useRuntimeConfig()
const METABASE_URL = config.metabaseUrl || 'http://metabase:3000'
const METABASE_API_KEY = config.metabaseApiKey || ''
const METABASE_EMAIL = config.metabaseEmail || 'claudeCode0@nucleoriofrio.com'
const METABASE_PASSWORD = config.metabasePassword || 'vK^NyZdZDH#p'
@@ -50,29 +51,37 @@ export async function getMetabaseToken(): Promise<string> {
/**
* Make an authenticated request to Metabase API
* Supports both API Key and Session Token authentication
*/
export async function metabaseFetch<T = any>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const token = await getMetabaseToken()
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers as Record<string, string>
}
// Prefer API Key if available
if (METABASE_API_KEY) {
headers['X-API-KEY'] = METABASE_API_KEY
} else {
const token = await getMetabaseToken()
headers['X-Metabase-Session'] = token
}
try {
const response = await $fetch<T>(`${METABASE_URL}${endpoint}`, {
...options,
headers: {
...options.headers,
'X-Metabase-Session': token,
'Content-Type': 'application/json'
}
headers
})
return response
} catch (error: any) {
console.error(`[Metabase] Request failed for ${endpoint}:`, error)
// If token is invalid, clear it and retry once
if (error.statusCode === 401) {
// If using session token and it's invalid, clear it and retry once
if (error.statusCode === 401 && !METABASE_API_KEY) {
console.log('[Metabase] Token invalid, clearing and retrying...')
sessionToken = null
tokenExpiry = 0
@@ -126,3 +135,36 @@ export async function queryMetabaseTable(databaseId: number, tableId: number, qu
}
})
}
/**
* Get all cards/questions
*/
export async function getMetabaseCards(filter?: string) {
const endpoint = filter ? `/api/card?f=${filter}` : '/api/card'
return metabaseFetch(endpoint)
}
/**
* Get a specific card by ID
*/
export async function getMetabaseCard(cardId: number) {
return metabaseFetch(`/api/card/${cardId}`)
}
/**
* Execute a card query with optional parameters
*/
export async function executeCardQuery(cardId: number, parameters?: any[]) {
const body = parameters ? { parameters } : {}
return metabaseFetch(`/api/card/${cardId}/query`, {
method: 'POST',
body
})
}
/**
* Execute a card query with cache (GET)
*/
export async function executeCardQueryCached(cardId: number) {
return metabaseFetch(`/api/card/${cardId}/query`)
}