feat: WhatsApp Nucleo con Nuxt 4 + Baileys v7
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 6m46s
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 6m46s
Reemplazo completo de Evolution API por implementación directa con Baileys. Características: - Dashboard completo con Nuxt UI v4 - Soporte para múltiples instancias de WhatsApp - Conexión via QR code o pairing code - Persistencia de mensajes en PostgreSQL - API REST para integraciones externas - Webhooks con firma HMAC - SSE para actualizaciones en tiempo real - Autenticación con Authentik
This commit is contained in:
103
app/pages/api-docs.vue
Normal file
103
app/pages/api-docs.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-[var(--wa-text)]">Documentacion de API</h1>
|
||||
<p class="text-[var(--wa-text-muted)]">Guia para integrar WhatsApp Nucleo con tus sistemas</p>
|
||||
</div>
|
||||
|
||||
<!-- Authentication -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">Autenticacion</h2>
|
||||
<p class="text-[var(--wa-text-muted)] mb-4">
|
||||
Todas las llamadas a la API externa requieren un API Key en el header <code class="bg-[var(--wa-bg-light)] px-2 py-1 rounded">Authorization</code>.
|
||||
</p>
|
||||
|
||||
<div class="bg-[var(--wa-bg)] p-4 rounded-lg font-mono text-sm">
|
||||
<span class="text-[var(--wa-text-muted)]"># Ejemplo de autenticacion</span><br>
|
||||
<span class="text-[var(--wa-blue)]">curl</span> -X POST https://whatsapp.nucleoriofrio.com/api/messages/send \<br>
|
||||
-H <span class="text-[var(--wa-green-light)]">"Authorization: Bearer YOUR_API_KEY"</span> \<br>
|
||||
-H <span class="text-[var(--wa-green-light)]">"Content-Type: application/json"</span> \<br>
|
||||
-d '{"instanceId": "...", "to": "...", "message": "..."}'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoints -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">Endpoints Disponibles</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Send Message -->
|
||||
<div class="border border-[var(--wa-border)] rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-green-600 text-white text-xs px-2 py-1 rounded font-semibold">POST</span>
|
||||
<code class="text-[var(--wa-text)]">/api/messages/send</code>
|
||||
</div>
|
||||
<p class="text-[var(--wa-text-muted)] text-sm mb-3">Envia un mensaje de texto a un numero de WhatsApp</p>
|
||||
|
||||
<div class="bg-[var(--wa-bg)] p-3 rounded text-sm font-mono">
|
||||
<span class="text-[var(--wa-text-muted)]">// Request body</span><br>
|
||||
{<br>
|
||||
"instanceId": <span class="text-[var(--wa-green-light)]">"uuid-de-instancia"</span>,<br>
|
||||
"to": <span class="text-[var(--wa-green-light)]">"5491123456789"</span>,<br>
|
||||
"message": <span class="text-[var(--wa-green-light)]">"Hola desde la API!"</span><br>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Instances -->
|
||||
<div class="border border-[var(--wa-border)] rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-blue-600 text-white text-xs px-2 py-1 rounded font-semibold">GET</span>
|
||||
<code class="text-[var(--wa-text)]">/api/instances</code>
|
||||
</div>
|
||||
<p class="text-[var(--wa-text-muted)] text-sm">Obtiene la lista de instancias disponibles</p>
|
||||
</div>
|
||||
|
||||
<!-- Get Instance Status -->
|
||||
<div class="border border-[var(--wa-border)] rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-blue-600 text-white text-xs px-2 py-1 rounded font-semibold">GET</span>
|
||||
<code class="text-[var(--wa-text)]">/api/instances/:id/status</code>
|
||||
</div>
|
||||
<p class="text-[var(--wa-text-muted)] text-sm">Obtiene el estado de conexion de una instancia</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhook Events -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">Eventos de Webhook</h2>
|
||||
<p class="text-[var(--wa-text-muted)] mb-4">
|
||||
Los webhooks envian notificaciones cuando ocurren estos eventos:
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="bg-[var(--wa-bg-light)] p-4 rounded-lg">
|
||||
<code class="text-[var(--wa-green-light)]">message.received</code>
|
||||
<p class="text-sm text-[var(--wa-text-muted)] mt-1">Se recibio un mensaje</p>
|
||||
</div>
|
||||
<div class="bg-[var(--wa-bg-light)] p-4 rounded-lg">
|
||||
<code class="text-[var(--wa-green-light)]">message.sent</code>
|
||||
<p class="text-sm text-[var(--wa-text-muted)] mt-1">Se envio un mensaje</p>
|
||||
</div>
|
||||
<div class="bg-[var(--wa-bg-light)] p-4 rounded-lg">
|
||||
<code class="text-[var(--wa-green-light)]">instance.connected</code>
|
||||
<p class="text-sm text-[var(--wa-text-muted)] mt-1">Instancia conectada</p>
|
||||
</div>
|
||||
<div class="bg-[var(--wa-bg-light)] p-4 rounded-lg">
|
||||
<code class="text-[var(--wa-green-light)]">instance.disconnected</code>
|
||||
<p class="text-sm text-[var(--wa-text-muted)] mt-1">Instancia desconectada</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'API Docs',
|
||||
icon: 'i-lucide-code'
|
||||
})
|
||||
</script>
|
||||
113
app/pages/index.vue
Normal file
113
app/pages/index.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<MetricCard
|
||||
title="Instancias Activas"
|
||||
:value="stats.connectedInstances"
|
||||
:total="stats.totalInstances"
|
||||
icon="i-lucide-smartphone"
|
||||
color="green"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Mensajes Hoy"
|
||||
:value="stats.messagesToday"
|
||||
icon="i-lucide-message-square"
|
||||
color="blue"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Webhooks Activos"
|
||||
:value="stats.activeWebhooks"
|
||||
icon="i-lucide-webhook"
|
||||
color="purple"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Chats Activos"
|
||||
:value="stats.activeChats"
|
||||
icon="i-lucide-users"
|
||||
color="amber"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Instances Overview -->
|
||||
<div class="instance-card p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-[var(--wa-text)]">Instancias</h3>
|
||||
<UButton
|
||||
to="/instances"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
trailing-icon="i-lucide-arrow-right"
|
||||
>
|
||||
Ver todas
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="instances.length === 0" class="text-center py-8">
|
||||
<UIcon name="i-lucide-smartphone" class="w-12 h-12 text-[var(--wa-text-muted)] mx-auto mb-3" />
|
||||
<p class="text-[var(--wa-text-muted)]">No hay instancias configuradas</p>
|
||||
<UButton
|
||||
to="/instances"
|
||||
variant="soft"
|
||||
class="mt-4"
|
||||
>
|
||||
Crear primera instancia
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="instance in instances.slice(0, 3)"
|
||||
:key="instance.id"
|
||||
class="flex items-center justify-between p-3 rounded-lg bg-[var(--wa-bg-light)]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:class="instance.status === 'connected' ? 'bg-[var(--wa-green-light)]' : 'bg-red-500'"
|
||||
/>
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">{{ instance.name }}</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">{{ instance.phoneNumber || 'Sin conectar' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge :status="instance.status" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="instance-card p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-[var(--wa-text)]">Actividad Reciente</h3>
|
||||
</div>
|
||||
|
||||
<div class="text-center py-8">
|
||||
<UIcon name="i-lucide-activity" class="w-12 h-12 text-[var(--wa-text-muted)] mx-auto mb-3" />
|
||||
<p class="text-[var(--wa-text-muted)]">La actividad aparecera aqui</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Dashboard',
|
||||
icon: 'i-lucide-layout-dashboard'
|
||||
})
|
||||
|
||||
// TODO: Conectar con datos reales
|
||||
const stats = ref({
|
||||
totalInstances: 0,
|
||||
connectedInstances: 0,
|
||||
messagesToday: 0,
|
||||
activeWebhooks: 0,
|
||||
activeChats: 0
|
||||
})
|
||||
|
||||
const instances = ref<any[]>([])
|
||||
</script>
|
||||
91
app/pages/instances/index.vue
Normal file
91
app/pages/instances/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-[var(--wa-text)]">Instancias de WhatsApp</h1>
|
||||
<p class="text-[var(--wa-text-muted)]">Gestiona tus conexiones de WhatsApp</p>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-lucide-plus"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
Nueva Instancia
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Instances Grid -->
|
||||
<div v-if="instances.length === 0" class="instance-card p-12 text-center">
|
||||
<UIcon name="i-lucide-smartphone" class="w-16 h-16 text-[var(--wa-text-muted)] mx-auto mb-4" />
|
||||
<h3 class="text-xl font-semibold text-[var(--wa-text)] mb-2">No hay instancias</h3>
|
||||
<p class="text-[var(--wa-text-muted)] mb-6">Crea tu primera instancia para comenzar a usar WhatsApp Nucleo</p>
|
||||
<UButton
|
||||
icon="i-lucide-plus"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
Crear Instancia
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<InstanceCard
|
||||
v-for="instance in instances"
|
||||
:key="instance.id"
|
||||
:instance="instance"
|
||||
@connect="handleConnect"
|
||||
@disconnect="handleDisconnect"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Create Instance Modal -->
|
||||
<CreateInstanceModal
|
||||
v-model:open="showCreateModal"
|
||||
@created="handleCreated"
|
||||
/>
|
||||
|
||||
<!-- QR Code Modal -->
|
||||
<QRCodeModal
|
||||
v-model:open="showQRModal"
|
||||
:instance-id="selectedInstanceId"
|
||||
:qr-code="qrCode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Instancias',
|
||||
icon: 'i-lucide-smartphone'
|
||||
})
|
||||
|
||||
const showCreateModal = ref(false)
|
||||
const showQRModal = ref(false)
|
||||
const selectedInstanceId = ref<string | null>(null)
|
||||
const qrCode = ref<string | null>(null)
|
||||
|
||||
// TODO: Conectar con API real
|
||||
const instances = ref<any[]>([])
|
||||
|
||||
const handleConnect = async (instanceId: string) => {
|
||||
selectedInstanceId.value = instanceId
|
||||
showQRModal.value = true
|
||||
// TODO: Iniciar conexion y obtener QR
|
||||
}
|
||||
|
||||
const handleDisconnect = async (instanceId: string) => {
|
||||
// TODO: Desconectar instancia
|
||||
console.log('Disconnecting', instanceId)
|
||||
}
|
||||
|
||||
const handleDelete = async (instanceId: string) => {
|
||||
// TODO: Eliminar instancia
|
||||
console.log('Deleting', instanceId)
|
||||
}
|
||||
|
||||
const handleCreated = (instance: any) => {
|
||||
instances.value.push(instance)
|
||||
showCreateModal.value = false
|
||||
}
|
||||
</script>
|
||||
116
app/pages/messages/index.vue
Normal file
116
app/pages/messages/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-[var(--wa-text)]">Mensajes</h1>
|
||||
<p class="text-[var(--wa-text-muted)]">Vista de conversaciones de todas las instancias</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instance Selector -->
|
||||
<div class="flex items-center gap-4">
|
||||
<USelectMenu
|
||||
v-model="selectedInstance"
|
||||
:items="instanceOptions"
|
||||
placeholder="Seleccionar instancia"
|
||||
class="w-64"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Chat Interface -->
|
||||
<div class="grid grid-cols-12 gap-4 h-[calc(100vh-300px)]">
|
||||
<!-- Chat List -->
|
||||
<div class="col-span-4 instance-card overflow-hidden flex flex-col">
|
||||
<div class="p-4 border-b border-[var(--wa-border)]">
|
||||
<UInput
|
||||
v-model="searchQuery"
|
||||
placeholder="Buscar conversacion..."
|
||||
icon="i-lucide-search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div v-if="chats.length === 0" class="p-8 text-center">
|
||||
<UIcon name="i-lucide-message-square" class="w-12 h-12 text-[var(--wa-text-muted)] mx-auto mb-3" />
|
||||
<p class="text-[var(--wa-text-muted)]">No hay conversaciones</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<ChatItem
|
||||
v-for="chat in filteredChats"
|
||||
:key="chat.id"
|
||||
:chat="chat"
|
||||
:active="selectedChat?.id === chat.id"
|
||||
@click="selectedChat = chat"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message View -->
|
||||
<div class="col-span-8 instance-card overflow-hidden flex flex-col">
|
||||
<div v-if="!selectedChat" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<UIcon name="i-lucide-message-circle" class="w-16 h-16 text-[var(--wa-text-muted)] mx-auto mb-4" />
|
||||
<p class="text-[var(--wa-text-muted)]">Selecciona una conversacion</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- Chat Header -->
|
||||
<div class="p-4 border-b border-[var(--wa-border)] flex items-center gap-3">
|
||||
<UAvatar :alt="selectedChat.name" size="md" />
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">{{ selectedChat.name }}</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">{{ selectedChat.jid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-2">
|
||||
<MessageBubble
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
:message="message"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="p-4 border-t border-[var(--wa-border)]">
|
||||
<MessageInput @send="handleSendMessage" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Mensajes',
|
||||
icon: 'i-lucide-message-square'
|
||||
})
|
||||
|
||||
const selectedInstance = ref(null)
|
||||
const searchQuery = ref('')
|
||||
const selectedChat = ref<any>(null)
|
||||
|
||||
// TODO: Conectar con API real
|
||||
const instanceOptions = ref<any[]>([])
|
||||
const chats = ref<any[]>([])
|
||||
const messages = ref<any[]>([])
|
||||
|
||||
const filteredChats = computed(() => {
|
||||
if (!searchQuery.value) return chats.value
|
||||
return chats.value.filter(chat =>
|
||||
chat.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const handleSendMessage = async (content: string) => {
|
||||
console.log('Sending:', content)
|
||||
// TODO: Implementar envio de mensajes
|
||||
}
|
||||
</script>
|
||||
138
app/pages/settings.vue
Normal file
138
app/pages/settings.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-[var(--wa-text)]">Configuracion</h1>
|
||||
<p class="text-[var(--wa-text-muted)]">Ajustes del sistema WhatsApp Nucleo</p>
|
||||
</div>
|
||||
|
||||
<!-- API Keys -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">API Keys</h2>
|
||||
<p class="text-[var(--wa-text-muted)] mb-4">
|
||||
Gestiona las claves de acceso para la API externa
|
||||
</p>
|
||||
|
||||
<div v-if="apiKeys.length === 0" class="text-center py-8">
|
||||
<p class="text-[var(--wa-text-muted)]">No hay API Keys configuradas</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3 mb-4">
|
||||
<div
|
||||
v-for="key in apiKeys"
|
||||
:key="key.id"
|
||||
class="flex items-center justify-between p-3 rounded-lg bg-[var(--wa-bg-light)]"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">{{ key.name }}</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">{{ key.keyPrefix }}... | Creada: {{ key.createdAt }}</p>
|
||||
</div>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
color="red"
|
||||
icon="i-lucide-trash-2"
|
||||
@click="deleteApiKey(key.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
icon="i-lucide-plus"
|
||||
variant="soft"
|
||||
@click="showCreateKeyModal = true"
|
||||
>
|
||||
Crear API Key
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- General Settings -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">Configuracion General</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">Limite de instancias</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">Maximo numero de instancias permitidas</p>
|
||||
</div>
|
||||
<UInput
|
||||
v-model.number="settings.maxInstances"
|
||||
type="number"
|
||||
class="w-24"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">Timeout de Webhooks</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">Tiempo maximo de espera para webhooks (ms)</p>
|
||||
</div>
|
||||
<UInput
|
||||
v-model.number="settings.webhookTimeout"
|
||||
type="number"
|
||||
class="w-24"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="font-medium text-[var(--wa-text)]">Reintentos de Webhook</p>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">Intentos de reenvio si falla</p>
|
||||
</div>
|
||||
<UInput
|
||||
v-model.number="settings.webhookRetries"
|
||||
type="number"
|
||||
class="w-24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Info -->
|
||||
<div class="instance-card p-6">
|
||||
<h2 class="text-lg font-semibold text-[var(--wa-text)] mb-4">Informacion del Sistema</h2>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p class="text-[var(--wa-text-muted)]">Version</p>
|
||||
<p class="text-[var(--wa-text)]">1.0.0</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[var(--wa-text-muted)]">Baileys</p>
|
||||
<p class="text-[var(--wa-text)]">v7.0.0-rc.9</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[var(--wa-text-muted)]">Node.js</p>
|
||||
<p class="text-[var(--wa-text)]">v22.x</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[var(--wa-text-muted)]">PostgreSQL</p>
|
||||
<p class="text-[var(--wa-text)]">v16</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Configuracion',
|
||||
icon: 'i-lucide-settings'
|
||||
})
|
||||
|
||||
const showCreateKeyModal = ref(false)
|
||||
|
||||
// TODO: Conectar con API real
|
||||
const apiKeys = ref<any[]>([])
|
||||
|
||||
const settings = ref({
|
||||
maxInstances: 10,
|
||||
webhookTimeout: 5000,
|
||||
webhookRetries: 3
|
||||
})
|
||||
|
||||
const deleteApiKey = async (keyId: string) => {
|
||||
console.log('Deleting API key', keyId)
|
||||
}
|
||||
</script>
|
||||
86
app/pages/webhooks/index.vue
Normal file
86
app/pages/webhooks/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-[var(--wa-text)]">Webhooks</h1>
|
||||
<p class="text-[var(--wa-text-muted)]">Configura notificaciones para eventos de WhatsApp</p>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-lucide-plus"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
Nuevo Webhook
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Webhooks List -->
|
||||
<div v-if="webhooks.length === 0" class="instance-card p-12 text-center">
|
||||
<UIcon name="i-lucide-webhook" class="w-16 h-16 text-[var(--wa-text-muted)] mx-auto mb-4" />
|
||||
<h3 class="text-xl font-semibold text-[var(--wa-text)] mb-2">No hay webhooks configurados</h3>
|
||||
<p class="text-[var(--wa-text-muted)] mb-6">Los webhooks te permiten recibir notificaciones en tiempo real cuando ocurren eventos en WhatsApp</p>
|
||||
<UButton
|
||||
icon="i-lucide-plus"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
Crear Webhook
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<WebhookCard
|
||||
v-for="webhook in webhooks"
|
||||
:key="webhook.id"
|
||||
:webhook="webhook"
|
||||
@edit="handleEdit"
|
||||
@delete="handleDelete"
|
||||
@test="handleTest"
|
||||
@toggle="handleToggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Create Webhook Modal -->
|
||||
<WebhookFormModal
|
||||
v-model:open="showCreateModal"
|
||||
:webhook="editingWebhook"
|
||||
@saved="handleSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
title: 'Webhooks',
|
||||
icon: 'i-lucide-webhook'
|
||||
})
|
||||
|
||||
const showCreateModal = ref(false)
|
||||
const editingWebhook = ref<any>(null)
|
||||
|
||||
// TODO: Conectar con API real
|
||||
const webhooks = ref<any[]>([])
|
||||
|
||||
const handleEdit = (webhook: any) => {
|
||||
editingWebhook.value = webhook
|
||||
showCreateModal.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (webhookId: string) => {
|
||||
console.log('Deleting webhook', webhookId)
|
||||
}
|
||||
|
||||
const handleTest = async (webhookId: string) => {
|
||||
console.log('Testing webhook', webhookId)
|
||||
}
|
||||
|
||||
const handleToggle = async (webhookId: string, active: boolean) => {
|
||||
console.log('Toggle webhook', webhookId, active)
|
||||
}
|
||||
|
||||
const handleSaved = (webhook: any) => {
|
||||
showCreateModal.value = false
|
||||
editingWebhook.value = null
|
||||
// TODO: Refresh list
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user