822 lines
26 KiB
Vue
822 lines
26 KiB
Vue
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Page Info Card -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-webhook"></Icon>
|
|
<h1 class="text-xl font-bold text-primary">Webhook Management</h1>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<p class="text-gray-600">
|
|
Configure and manage webhook endpoints for delivery status updates. Monitor webhook performance,
|
|
manage retry policies, and view delivery logs.
|
|
</p>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Webhook Statistics -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-6 mb-6">
|
|
<rs-card
|
|
v-for="(stat, index) in webhookStats"
|
|
:key="index"
|
|
class="transition-all duration-300 hover:shadow-lg"
|
|
>
|
|
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
|
|
<div
|
|
class="p-5 flex justify-center items-center rounded-2xl transition-all duration-300"
|
|
:class="stat.bgColor"
|
|
>
|
|
<Icon class="text-3xl" :name="stat.icon" :class="stat.iconColor"></Icon>
|
|
</div>
|
|
<div class="flex-1 truncate">
|
|
<span class="block font-bold text-2xl leading-tight" :class="stat.textColor">
|
|
{{ stat.value }}
|
|
</span>
|
|
<span class="text-sm font-medium text-gray-600">
|
|
{{ stat.title }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Webhook Endpoints -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-webhook"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Webhook Endpoints</h2>
|
|
</div>
|
|
<rs-button variant="primary" @click="showAddWebhookModal = true">
|
|
<Icon class="mr-1" name="ic:outline-add"></Icon>
|
|
Add Webhook
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="webhook in webhooks"
|
|
:key="webhook.id"
|
|
class="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<h3 class="font-semibold text-lg">{{ webhook.name }}</h3>
|
|
<rs-badge :variant="webhook.enabled ? 'success' : 'secondary'" size="sm">
|
|
{{ webhook.enabled ? 'Active' : 'Disabled' }}
|
|
</rs-badge>
|
|
<rs-badge :variant="getHealthVariant(webhook.health)" size="sm">
|
|
{{ webhook.health }}
|
|
</rs-badge>
|
|
</div>
|
|
<div class="text-sm text-gray-600 mb-2">{{ webhook.description }}</div>
|
|
<div class="font-mono text-sm bg-gray-50 p-2 rounded">{{ webhook.url }}</div>
|
|
</div>
|
|
<div class="flex gap-2 ml-4">
|
|
<rs-button size="sm" variant="secondary-outline" @click="editWebhook(webhook)">
|
|
<Icon name="ic:outline-edit"></Icon>
|
|
</rs-button>
|
|
<rs-button size="sm" variant="secondary-outline" @click="testWebhook(webhook)">
|
|
<Icon name="ic:outline-send"></Icon>
|
|
</rs-button>
|
|
<rs-button size="sm" variant="danger-outline" @click="deleteWebhook(webhook)">
|
|
<Icon name="ic:outline-delete"></Icon>
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Webhook Details -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Events</label>
|
|
<div class="flex flex-wrap gap-1">
|
|
<rs-badge
|
|
v-for="event in webhook.events"
|
|
:key="event"
|
|
variant="info"
|
|
size="xs"
|
|
>
|
|
{{ event }}
|
|
</rs-badge>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Success Rate</label>
|
|
<div class="font-semibold">{{ webhook.successRate }}%</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Avg Response Time</label>
|
|
<div class="font-semibold">{{ webhook.avgResponseTime }}ms</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Performance Chart -->
|
|
<div class="mb-4">
|
|
<label class="block text-xs font-medium text-gray-500 mb-2">Performance Trend (24h)</label>
|
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
class="h-2 rounded-full transition-all duration-300"
|
|
:class="getPerformanceClass(webhook.performance)"
|
|
:style="{ width: webhook.performance + '%' }"
|
|
></div>
|
|
</div>
|
|
<div class="text-xs text-gray-500 mt-1">{{ webhook.performance }}% performance score</div>
|
|
</div>
|
|
|
|
<!-- Recent Deliveries -->
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-2">Recent Deliveries</label>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="delivery in webhook.recentDeliveries"
|
|
:key="delivery.id"
|
|
class="flex items-center justify-between p-2 bg-gray-50 rounded text-sm"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<Icon
|
|
:name="delivery.success ? 'ic:outline-check-circle' : 'ic:outline-error'"
|
|
:class="delivery.success ? 'text-green-500' : 'text-red-500'"
|
|
></Icon>
|
|
<span>{{ delivery.event }}</span>
|
|
<span class="text-gray-500">{{ delivery.timestamp }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-gray-500">{{ delivery.responseTime }}ms</span>
|
|
<span :class="delivery.success ? 'text-green-600' : 'text-red-600'">
|
|
{{ delivery.status }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Delivery Logs -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-history"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Delivery Logs</h2>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<rs-button size="sm" variant="secondary-outline" @click="refreshLogs">
|
|
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
|
|
Refresh
|
|
</rs-button>
|
|
<rs-button size="sm" variant="secondary-outline" @click="exportLogs">
|
|
<Icon class="mr-1" name="ic:outline-download"></Icon>
|
|
Export
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<!-- Filters -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
|
<FormKit
|
|
type="select"
|
|
v-model="logFilters.webhook"
|
|
:options="webhookFilterOptions"
|
|
placeholder="All Webhooks"
|
|
label="Webhook"
|
|
/>
|
|
<FormKit
|
|
type="select"
|
|
v-model="logFilters.event"
|
|
:options="eventFilterOptions"
|
|
placeholder="All Events"
|
|
label="Event"
|
|
/>
|
|
<FormKit
|
|
type="select"
|
|
v-model="logFilters.status"
|
|
:options="statusFilterOptions"
|
|
placeholder="All Statuses"
|
|
label="Status"
|
|
/>
|
|
<FormKit
|
|
type="date"
|
|
v-model="logFilters.date"
|
|
label="Date"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Logs Table -->
|
|
<rs-table
|
|
:field="logTableFields"
|
|
:data="filteredLogs"
|
|
:advanced="true"
|
|
:options="{ striped: true, hover: true }"
|
|
:optionsAdvanced="{
|
|
sortable: true,
|
|
filterable: true,
|
|
responsive: true,
|
|
}"
|
|
:pageSize="20"
|
|
>
|
|
<template #webhook="{ row }">
|
|
<div class="font-medium">{{ row.webhookName }}</div>
|
|
<div class="text-xs text-gray-500">{{ row.url }}</div>
|
|
</template>
|
|
|
|
<template #event="{ row }">
|
|
<rs-badge variant="info" size="sm">
|
|
{{ row.event }}
|
|
</rs-badge>
|
|
</template>
|
|
|
|
<template #status="{ row }">
|
|
<rs-badge :variant="row.success ? 'success' : 'danger'" size="sm">
|
|
{{ row.status }}
|
|
</rs-badge>
|
|
</template>
|
|
|
|
<template #responseTime="{ row }">
|
|
<span :class="row.responseTime > 1000 ? 'text-red-600' : 'text-green-600'">
|
|
{{ row.responseTime }}ms
|
|
</span>
|
|
</template>
|
|
|
|
<template #actions="{ row }">
|
|
<div class="flex gap-2">
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="viewLogDetails(row)"
|
|
>
|
|
<Icon name="ic:outline-visibility"></Icon>
|
|
</rs-button>
|
|
<rs-button
|
|
size="sm"
|
|
variant="secondary-outline"
|
|
@click="retryWebhook(row)"
|
|
v-if="!row.success"
|
|
>
|
|
<Icon name="ic:outline-refresh"></Icon>
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
</rs-table>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Add/Edit Webhook Modal -->
|
|
<rs-modal v-model="showAddWebhookModal" size="lg">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold">{{ editingWebhook ? 'Edit' : 'Add' }} Webhook</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-6">
|
|
|
|
<!-- Basic Information -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormKit
|
|
type="text"
|
|
v-model="webhookForm.name"
|
|
label="Webhook Name"
|
|
placeholder="Enter webhook name"
|
|
validation="required"
|
|
/>
|
|
<FormKit
|
|
type="url"
|
|
v-model="webhookForm.url"
|
|
label="Endpoint URL"
|
|
placeholder="https://api.example.com/webhook"
|
|
validation="required|url"
|
|
/>
|
|
</div>
|
|
|
|
<FormKit
|
|
type="textarea"
|
|
v-model="webhookForm.description"
|
|
label="Description"
|
|
placeholder="Describe what this webhook is used for"
|
|
rows="2"
|
|
/>
|
|
|
|
<!-- Events Selection -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Events to Subscribe</label>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
|
|
<div
|
|
v-for="event in availableEvents"
|
|
:key="event.value"
|
|
class="flex items-center"
|
|
>
|
|
<FormKit
|
|
type="checkbox"
|
|
v-model="webhookForm.events"
|
|
:value="event.value"
|
|
:label="event.label"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security -->
|
|
<div>
|
|
<h4 class="font-semibold mb-3">Security Settings</h4>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormKit
|
|
type="password"
|
|
v-model="webhookForm.secret"
|
|
label="Secret Key"
|
|
placeholder="Optional secret for signature verification"
|
|
help="Used to sign webhook payloads for verification"
|
|
/>
|
|
<FormKit
|
|
type="select"
|
|
v-model="webhookForm.authType"
|
|
label="Authentication Type"
|
|
:options="authTypeOptions"
|
|
/>
|
|
</div>
|
|
<div v-if="webhookForm.authType === 'bearer'" class="mt-4">
|
|
<FormKit
|
|
type="password"
|
|
v-model="webhookForm.bearerToken"
|
|
label="Bearer Token"
|
|
placeholder="Enter bearer token"
|
|
/>
|
|
</div>
|
|
<div v-if="webhookForm.authType === 'basic'" class="grid grid-cols-2 gap-4 mt-4">
|
|
<FormKit
|
|
type="text"
|
|
v-model="webhookForm.username"
|
|
label="Username"
|
|
placeholder="Enter username"
|
|
/>
|
|
<FormKit
|
|
type="password"
|
|
v-model="webhookForm.password"
|
|
label="Password"
|
|
placeholder="Enter password"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Retry Settings -->
|
|
<div>
|
|
<h4 class="font-semibold mb-3">Retry Settings</h4>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
<FormKit
|
|
type="number"
|
|
v-model="webhookForm.maxRetries"
|
|
label="Max Retries"
|
|
placeholder="3"
|
|
min="0"
|
|
max="10"
|
|
/>
|
|
<FormKit
|
|
type="number"
|
|
v-model="webhookForm.retryDelay"
|
|
label="Retry Delay (seconds)"
|
|
placeholder="60"
|
|
min="1"
|
|
max="3600"
|
|
/>
|
|
<FormKit
|
|
type="number"
|
|
v-model="webhookForm.timeout"
|
|
label="Timeout (seconds)"
|
|
placeholder="30"
|
|
min="1"
|
|
max="300"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Webhook -->
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h4 class="font-semibold mb-3">Test Webhook</h4>
|
|
<div class="flex items-center gap-3">
|
|
<rs-button variant="secondary" @click="testWebhookEndpoint">
|
|
<Icon class="mr-1" name="ic:outline-send"></Icon>
|
|
Send Test
|
|
</rs-button>
|
|
<div v-if="testResult" class="flex items-center gap-2">
|
|
<Icon
|
|
:name="testResult.success ? 'ic:outline-check-circle' : 'ic:outline-error'"
|
|
:class="testResult.success ? 'text-green-500' : 'text-red-500'"
|
|
></Icon>
|
|
<span :class="testResult.success ? 'text-green-600' : 'text-red-600'">
|
|
{{ testResult.message }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2">
|
|
<rs-button variant="outline" @click="showAddWebhookModal = false">Cancel</rs-button>
|
|
<rs-button variant="primary" @click="saveWebhook">
|
|
{{ editingWebhook ? 'Update' : 'Create' }} Webhook
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
</rs-modal>
|
|
|
|
<!-- Log Details Modal -->
|
|
<rs-modal v-model="showLogModal" size="lg">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold">Webhook Delivery Details</h3>
|
|
</template>
|
|
<template #body>
|
|
<div v-if="selectedLog" class="space-y-6">
|
|
|
|
<!-- Basic Info -->
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Webhook</label>
|
|
<div class="font-medium">{{ selectedLog.webhookName }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Event</label>
|
|
<rs-badge variant="info">{{ selectedLog.event }}</rs-badge>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Status</label>
|
|
<rs-badge :variant="selectedLog.success ? 'success' : 'danger'">
|
|
{{ selectedLog.status }}
|
|
</rs-badge>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Response Time</label>
|
|
<div class="font-medium">{{ selectedLog.responseTime }}ms</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Request Details -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Request Payload</label>
|
|
<div class="bg-gray-50 rounded-lg p-3">
|
|
<pre class="text-xs overflow-x-auto">{{ JSON.stringify(selectedLog.payload, null, 2) }}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Response Details -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Response</label>
|
|
<div class="bg-gray-50 rounded-lg p-3">
|
|
<div class="text-sm mb-2">
|
|
<strong>Status Code:</strong> {{ selectedLog.responseCode }}
|
|
</div>
|
|
<div class="text-sm mb-2">
|
|
<strong>Headers:</strong>
|
|
</div>
|
|
<pre class="text-xs overflow-x-auto mb-2">{{ JSON.stringify(selectedLog.responseHeaders, null, 2) }}</pre>
|
|
<div class="text-sm mb-2">
|
|
<strong>Body:</strong>
|
|
</div>
|
|
<pre class="text-xs overflow-x-auto">{{ selectedLog.responseBody }}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Details (if any) -->
|
|
<div v-if="selectedLog.error">
|
|
<label class="block text-sm font-medium text-gray-500 mb-1">Error Details</label>
|
|
<div class="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
<div class="text-sm text-red-800">{{ selectedLog.error }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2">
|
|
<rs-button variant="outline" @click="showLogModal = false">Close</rs-button>
|
|
<rs-button
|
|
variant="primary"
|
|
@click="retryWebhook(selectedLog)"
|
|
v-if="!selectedLog?.success"
|
|
>
|
|
Retry Delivery
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
</rs-modal>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
definePageMeta({
|
|
title: "Webhook Management",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{
|
|
name: "Dashboard",
|
|
path: "/dashboard",
|
|
},
|
|
{
|
|
name: "Notification",
|
|
path: "/notification",
|
|
},
|
|
{
|
|
name: "Delivery Engine",
|
|
path: "/notification/delivery",
|
|
},
|
|
{
|
|
name: "Webhooks",
|
|
path: "/notification/delivery/webhooks",
|
|
type: "current",
|
|
},
|
|
],
|
|
});
|
|
|
|
import { ref, reactive, computed } from "vue";
|
|
|
|
// Modal states
|
|
const showAddWebhookModal = ref(false);
|
|
const showLogModal = ref(false);
|
|
const editingWebhook = ref(null);
|
|
const selectedLog = ref(null);
|
|
const testResult = ref(null);
|
|
|
|
// Form data
|
|
const webhookForm = reactive({
|
|
name: "",
|
|
url: "",
|
|
description: "",
|
|
events: [],
|
|
secret: "",
|
|
authType: "none",
|
|
bearerToken: "",
|
|
username: "",
|
|
password: "",
|
|
maxRetries: 3,
|
|
retryDelay: 60,
|
|
timeout: 30,
|
|
enabled: true,
|
|
});
|
|
|
|
// Filter states
|
|
const logFilters = reactive({
|
|
webhook: "",
|
|
event: "",
|
|
status: "",
|
|
date: "",
|
|
});
|
|
|
|
// Statistics
|
|
const webhookStats = ref([
|
|
{
|
|
title: "Active Webhooks",
|
|
value: "8",
|
|
icon: "ic:outline-webhook",
|
|
bgColor: "bg-blue-100",
|
|
iconColor: "text-blue-600",
|
|
textColor: "text-blue-600",
|
|
},
|
|
{
|
|
title: "Deliveries Today",
|
|
value: "1.2K",
|
|
icon: "ic:outline-send",
|
|
bgColor: "bg-green-100",
|
|
iconColor: "text-green-600",
|
|
textColor: "text-green-600",
|
|
},
|
|
{
|
|
title: "Success Rate",
|
|
value: "98.5%",
|
|
icon: "ic:outline-trending-up",
|
|
bgColor: "bg-purple-100",
|
|
iconColor: "text-purple-600",
|
|
textColor: "text-purple-600",
|
|
},
|
|
{
|
|
title: "Failed Deliveries",
|
|
value: "18",
|
|
icon: "ic:outline-error",
|
|
bgColor: "bg-red-100",
|
|
iconColor: "text-red-600",
|
|
textColor: "text-red-600",
|
|
},
|
|
]);
|
|
|
|
// Webhook data
|
|
const webhooks = ref([
|
|
{
|
|
id: 1,
|
|
name: "CRM Integration",
|
|
description: "Delivery status updates for CRM system",
|
|
url: "https://api.crm.com/webhooks/delivery",
|
|
enabled: true,
|
|
health: "Healthy",
|
|
events: ["delivered", "opened", "bounced"],
|
|
successRate: 99.2,
|
|
avgResponseTime: 150,
|
|
performance: 95,
|
|
recentDeliveries: [
|
|
{ id: 1, event: "delivered", timestamp: "2 min ago", success: true, status: "200", responseTime: 140 },
|
|
{ id: 2, event: "opened", timestamp: "5 min ago", success: true, status: "200", responseTime: 160 },
|
|
{ id: 3, event: "bounced", timestamp: "8 min ago", success: false, status: "500", responseTime: 0 },
|
|
],
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Analytics Platform",
|
|
description: "Send delivery metrics to analytics dashboard",
|
|
url: "https://analytics.example.com/webhook",
|
|
enabled: true,
|
|
health: "Warning",
|
|
events: ["sent", "delivered", "failed"],
|
|
successRate: 87.5,
|
|
avgResponseTime: 2100,
|
|
performance: 78,
|
|
recentDeliveries: [
|
|
{ id: 4, event: "sent", timestamp: "1 min ago", success: true, status: "200", responseTime: 1900 },
|
|
{ id: 5, event: "delivered", timestamp: "3 min ago", success: false, status: "timeout", responseTime: 0 },
|
|
{ id: 6, event: "failed", timestamp: "6 min ago", success: true, status: "200", responseTime: 2300 },
|
|
],
|
|
},
|
|
]);
|
|
|
|
// Delivery logs
|
|
const deliveryLogs = ref([
|
|
{
|
|
id: 1,
|
|
webhookId: 1,
|
|
webhookName: "CRM Integration",
|
|
url: "https://api.crm.com/webhooks/delivery",
|
|
event: "delivered",
|
|
success: true,
|
|
status: "200 OK",
|
|
responseTime: 140,
|
|
timestamp: "2024-01-15 10:30:00",
|
|
payload: {
|
|
messageId: "msg_001",
|
|
event: "delivered",
|
|
timestamp: "2024-01-15T10:30:00Z",
|
|
channel: "email",
|
|
recipient: "user@example.com"
|
|
},
|
|
responseCode: 200,
|
|
responseHeaders: { "content-type": "application/json" },
|
|
responseBody: '{"status": "received"}',
|
|
error: null,
|
|
},
|
|
// Add more logs...
|
|
]);
|
|
|
|
// Form options
|
|
const availableEvents = [
|
|
{ label: "Message Queued", value: "queued" },
|
|
{ label: "Message Sent", value: "sent" },
|
|
{ label: "Message Delivered", value: "delivered" },
|
|
{ label: "Message Opened", value: "opened" },
|
|
{ label: "Message Failed", value: "failed" },
|
|
{ label: "Message Bounced", value: "bounced" },
|
|
];
|
|
|
|
const authTypeOptions = [
|
|
{ label: "None", value: "none" },
|
|
{ label: "Bearer Token", value: "bearer" },
|
|
{ label: "Basic Auth", value: "basic" },
|
|
];
|
|
|
|
// Filter options
|
|
const webhookFilterOptions = computed(() => [
|
|
{ label: "All Webhooks", value: "" },
|
|
...webhooks.value.map(w => ({ label: w.name, value: w.id }))
|
|
]);
|
|
|
|
const eventFilterOptions = [
|
|
{ label: "All Events", value: "" },
|
|
...availableEvents,
|
|
];
|
|
|
|
const statusFilterOptions = [
|
|
{ label: "All Statuses", value: "" },
|
|
{ label: "Success", value: "success" },
|
|
{ label: "Failed", value: "failed" },
|
|
];
|
|
|
|
// Table configuration
|
|
const logTableFields = ref([
|
|
{ key: "timestamp", label: "Timestamp", sortable: true },
|
|
{ key: "webhook", label: "Webhook", sortable: true },
|
|
{ key: "event", label: "Event", sortable: true },
|
|
{ key: "status", label: "Status", sortable: true },
|
|
{ key: "responseTime", label: "Response Time", sortable: true },
|
|
{ key: "actions", label: "Actions", sortable: false },
|
|
]);
|
|
|
|
// Computed
|
|
const filteredLogs = computed(() => {
|
|
let filtered = deliveryLogs.value;
|
|
|
|
if (logFilters.webhook) {
|
|
filtered = filtered.filter(log => log.webhookId === logFilters.webhook);
|
|
}
|
|
|
|
if (logFilters.event) {
|
|
filtered = filtered.filter(log => log.event === logFilters.event);
|
|
}
|
|
|
|
if (logFilters.status) {
|
|
const isSuccess = logFilters.status === "success";
|
|
filtered = filtered.filter(log => log.success === isSuccess);
|
|
}
|
|
|
|
return filtered;
|
|
});
|
|
|
|
// Methods
|
|
function getHealthVariant(health) {
|
|
const variants = {
|
|
"Healthy": "success",
|
|
"Warning": "warning",
|
|
"Critical": "danger",
|
|
};
|
|
return variants[health] || "secondary";
|
|
}
|
|
|
|
function getPerformanceClass(performance) {
|
|
if (performance >= 90) return "bg-green-500";
|
|
if (performance >= 70) return "bg-yellow-500";
|
|
return "bg-red-500";
|
|
}
|
|
|
|
function editWebhook(webhook) {
|
|
editingWebhook.value = webhook;
|
|
// Populate form with webhook data
|
|
Object.keys(webhookForm).forEach(key => {
|
|
if (webhook[key] !== undefined) {
|
|
webhookForm[key] = webhook[key];
|
|
}
|
|
});
|
|
showAddWebhookModal.value = true;
|
|
}
|
|
|
|
function testWebhook(webhook) {
|
|
console.log("Testing webhook:", webhook.name);
|
|
// Implementation for testing webhook
|
|
}
|
|
|
|
function deleteWebhook(webhook) {
|
|
console.log("Deleting webhook:", webhook.name);
|
|
// Implementation for deleting webhook
|
|
}
|
|
|
|
function saveWebhook() {
|
|
console.log("Saving webhook:", webhookForm);
|
|
showAddWebhookModal.value = false;
|
|
// Reset form
|
|
Object.keys(webhookForm).forEach(key => {
|
|
if (typeof webhookForm[key] === 'string') webhookForm[key] = '';
|
|
if (typeof webhookForm[key] === 'number') webhookForm[key] = 0;
|
|
if (Array.isArray(webhookForm[key])) webhookForm[key] = [];
|
|
if (typeof webhookForm[key] === 'boolean') webhookForm[key] = true;
|
|
});
|
|
}
|
|
|
|
function testWebhookEndpoint() {
|
|
console.log("Testing webhook endpoint...");
|
|
testResult.value = null;
|
|
|
|
// Simulate test
|
|
setTimeout(() => {
|
|
testResult.value = {
|
|
success: Math.random() > 0.3,
|
|
message: Math.random() > 0.3 ? "Test successful" : "Connection failed - Check URL and credentials"
|
|
};
|
|
}, 2000);
|
|
}
|
|
|
|
function viewLogDetails(log) {
|
|
selectedLog.value = log;
|
|
showLogModal.value = true;
|
|
}
|
|
|
|
function retryWebhook(log) {
|
|
console.log("Retrying webhook delivery:", log);
|
|
// Implementation for retrying webhook delivery
|
|
}
|
|
|
|
function refreshLogs() {
|
|
console.log("Refreshing logs...");
|
|
// Implementation for refreshing logs
|
|
}
|
|
|
|
function exportLogs() {
|
|
console.log("Exporting logs...");
|
|
// Implementation for exporting logs
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style> |