diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..045baea --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "chrome-devtools": { + "command": "npx", + "args": ["-y", "chrome-devtools-mcp@latest"] + } + } +} diff --git a/nuxt4-app/app/components/UserMenu.vue b/nuxt4-app/app/components/UserMenu.vue new file mode 100644 index 0000000..7500f92 --- /dev/null +++ b/nuxt4-app/app/components/UserMenu.vue @@ -0,0 +1,48 @@ + + + + + + + + + + {{ user.name || user.username }} + + + {{ item.label }} + + + + + + + diff --git a/nuxt4-app/app/composables/useAuth.ts b/nuxt4-app/app/composables/useAuth.ts new file mode 100644 index 0000000..97e6ac3 --- /dev/null +++ b/nuxt4-app/app/composables/useAuth.ts @@ -0,0 +1,38 @@ +export interface AuthUser { + username: string | null + email: string | null + name: string | null + uid: string | null + groups: string[] + authenticated: boolean +} + +export const useAuth = () => { + const user = useState('auth-user', () => null) + const loading = useState('auth-loading', () => false) + + const fetchUser = async () => { + loading.value = true + try { + const data = await $fetch('/api/auth/user') + user.value = data + } catch (error) { + console.error('Error fetching user:', error) + user.value = null + } finally { + loading.value = false + } + } + + const logout = () => { + // Authentik maneja el logout, redirigir a la URL de logout + window.location.href = '/outpost.goauthentik.io/sign_out' + } + + return { + user: readonly(user), + loading: readonly(loading), + fetchUser, + logout + } +} diff --git a/nuxt4-app/app/layouts/dashboard.vue b/nuxt4-app/app/layouts/dashboard.vue index f8d5e0a..24a8cac 100644 --- a/nuxt4-app/app/layouts/dashboard.vue +++ b/nuxt4-app/app/layouts/dashboard.vue @@ -16,6 +16,7 @@ + diff --git a/nuxt4-app/nuxt.config.ts b/nuxt4-app/nuxt.config.ts index 1cbbabe..66132aa 100644 --- a/nuxt4-app/nuxt.config.ts +++ b/nuxt4-app/nuxt.config.ts @@ -17,6 +17,17 @@ export default defineNuxtConfig({ // Optimize build vite: { plugins: [disableImportProtection()], + server: { + hmr: { + clientPort: 443, + protocol: 'wss' + }, + allowedHosts: [ + '.nucleoriofrio.com', + 'devserver.nucleoriofrio.com', + 'analitica.nucleoriofrio.com' + ] + }, build: { cssCodeSplit: true, rollupOptions: { @@ -30,11 +41,12 @@ export default defineNuxtConfig({ } }, app: { + baseURL: process.env.BASE_URL || '/', head: { link: [ - { rel: 'icon', type: 'image/png', href: '/icons/icon-192.png' }, - { rel: 'apple-touch-icon', sizes: '192x192', href: '/icons/icon-192.png' }, - { rel: 'manifest', href: '/manifest.webmanifest' } + { rel: 'icon', type: 'image/png', href: `${process.env.BASE_URL || ''}/icons/icon-192.png` }, + { rel: 'apple-touch-icon', sizes: '192x192', href: `${process.env.BASE_URL || ''}/icons/icon-192.png` }, + { rel: 'manifest', href: `${process.env.BASE_URL || ''}/manifest.webmanifest` } ], meta: [ { name: 'theme-color', content: '#1b1209' }, @@ -45,18 +57,15 @@ export default defineNuxtConfig({ } }, nitro: { + baseURL: process.env.BASE_URL || '/', + experimental: { + openAPI: true + }, routeRules: { - '/**': { - headers: { - 'Cross-Origin-Embedder-Policy': 'require-corp', - 'Cross-Origin-Opener-Policy': 'same-origin' - } - }, '/manifest.webmanifest': { headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - 'Content-Type': 'application/manifest+json' + 'Content-Type': 'application/manifest+json', + 'Cache-Control': 'public, max-age=3600' } }, '/sw.js': { @@ -76,8 +85,8 @@ export default defineNuxtConfig({ name: 'Analítica Núcleo Data Studio', short_name: 'Analítica', description: 'Explora y valida tus tablas Supabase desde un único panel en modo lectura.', - start_url: '/', - scope: '/', + start_url: process.env.BASE_URL || '/', + scope: process.env.BASE_URL || '/', display: 'standalone', background_color: '#1b1209', theme_color: '#c08040', diff --git a/nuxt4-app/server/api/auth/user.get.ts b/nuxt4-app/server/api/auth/user.get.ts new file mode 100644 index 0000000..b3d0f44 --- /dev/null +++ b/nuxt4-app/server/api/auth/user.get.ts @@ -0,0 +1,15 @@ +export default defineEventHandler((event) => { + const headers = getHeaders(event) + + // Authentik envía información del usuario en headers específicos + const user = { + username: headers['x-authentik-username'] || null, + email: headers['x-authentik-email'] || null, + name: headers['x-authentik-name'] || null, + uid: headers['x-authentik-uid'] || null, + groups: headers['x-authentik-groups'] ? headers['x-authentik-groups'].split(',') : [], + authenticated: !!headers['x-authentik-username'] + } + + return user +}) diff --git a/nuxt4-app/server/middleware/cors.ts b/nuxt4-app/server/middleware/cors.ts new file mode 100644 index 0000000..5323e55 --- /dev/null +++ b/nuxt4-app/server/middleware/cors.ts @@ -0,0 +1,32 @@ +export default defineEventHandler((event) => { + const origin = getHeader(event, 'origin') + const path = event.path || '' + + // Rutas públicas que siempre permiten CORS desde cualquier origen + const publicRoutes = ['/manifest.webmanifest', '/sw.js', '/workbox-', '/_nuxt/', '/icons/', '/screenshots/'] + const isPublicRoute = publicRoutes.some(route => path.startsWith(route)) + + if (isPublicRoute) { + setHeaders(event, { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400' + }) + } else if (origin && (origin.endsWith('.nucleoriofrio.com') || origin === 'https://nucleoriofrio.com')) { + // Permitir CORS desde cualquier subdominio de .nucleoriofrio.com para otras rutas + setHeaders(event, { + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With', + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Max-Age': '86400' + }) + } + + // Manejar preflight requests + if (getMethod(event) === 'OPTIONS') { + event.node.res.statusCode = 204 + event.node.res.end() + } +})
+ {{ user.name || user.username }} +
+ {{ item.label }} +