956 lines
28 KiB
Vue
956 lines
28 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-send"></Icon>
|
|
<h1 class="text-xl font-bold text-primary">Notification Delivery Engine</h1>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<p class="text-gray-600">
|
|
The heart of the system responsible for reliably delivering notifications to
|
|
users across multiple channels. Features intelligent routing, fault-tolerance,
|
|
real-time tracking, and deep observability.
|
|
</p>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Quick Stats -->
|
|
<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 deliveryStats"
|
|
: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 bg-primary/20 rounded-2xl transition-all duration-300 hover:bg-primary/30"
|
|
>
|
|
<Icon class="text-primary text-3xl" :name="stat.icon"></Icon>
|
|
</div>
|
|
<div class="flex-1 truncate">
|
|
<span class="block font-bold text-2xl leading-tight text-primary">
|
|
{{ stat.value }}
|
|
</span>
|
|
<span class="text-sm font-medium text-gray-600">
|
|
{{ stat.title }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Main Features Grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Multi-Channel Delivery -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-alt-route"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Multi-Channel Delivery</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showChannelModal = true"
|
|
>
|
|
Configure
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="channel in channels"
|
|
:key="channel.type"
|
|
class="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" :name="channel.icon"></Icon>
|
|
<span class="font-semibold">{{ channel.name }}</span>
|
|
</div>
|
|
<rs-badge
|
|
:variant="channel.status === 'active' ? 'success' : 'secondary'"
|
|
>
|
|
{{ channel.status }}
|
|
</rs-badge>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-600">Provider:</span>
|
|
<span class="ml-2 font-medium">{{ channel.provider }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600">Success Rate:</span>
|
|
<span class="ml-2 font-medium">{{ channel.successRate }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Real-time Status Tracking -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-track-changes"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">
|
|
Real-time Status Tracking
|
|
</h2>
|
|
</div>
|
|
<rs-button size="sm" variant="primary-outline" @click="refreshStatusData">
|
|
Refresh
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="status in deliveryStatuses"
|
|
:key="status.stage"
|
|
class="flex items-center justify-between p-3 border border-gray-200 rounded-lg"
|
|
>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2" :name="status.icon" :class="status.color"></Icon>
|
|
<span class="font-medium">{{ status.stage }}</span>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="font-bold text-lg">{{ status.count }}</div>
|
|
<div class="text-xs text-gray-500">{{ status.percentage }}%</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Fallback Mechanisms -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-route"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Fallback Mechanisms</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showFallbackModal = true"
|
|
>
|
|
Manage
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="fallback in fallbackRules"
|
|
:key="fallback.id"
|
|
class="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="font-semibold">{{ fallback.name }}</span>
|
|
<rs-badge :variant="fallback.enabled ? 'success' : 'secondary'">
|
|
{{ fallback.enabled ? "Active" : "Disabled" }}
|
|
</rs-badge>
|
|
</div>
|
|
<div class="text-sm text-gray-600">
|
|
<div>Primary: {{ fallback.primary }}</div>
|
|
<div>Fallback: {{ fallback.fallback }}</div>
|
|
<div>Trigger: {{ fallback.trigger }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Delivery Optimization -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-tune"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Delivery Optimization</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showOptimizationModal = true"
|
|
>
|
|
Settings
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h4 class="font-semibold mb-2">Smart Routing</h4>
|
|
<div class="text-sm text-gray-600 space-y-1">
|
|
<div>Best performing provider per region</div>
|
|
<div>Dynamic route selection based on success rates</div>
|
|
<div>Geographic optimization enabled</div>
|
|
</div>
|
|
</div>
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h4 class="font-semibold mb-2">Timing Optimization</h4>
|
|
<div class="text-sm text-gray-600 space-y-1">
|
|
<div>Off-peak scheduling for non-critical messages</div>
|
|
<div>Timezone-aware delivery</div>
|
|
<div>Quiet hours enforcement</div>
|
|
</div>
|
|
</div>
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<h4 class="font-semibold mb-2">Rate Limiting</h4>
|
|
<div class="text-sm text-gray-600 space-y-1">
|
|
<div>Bulk traffic throttling active</div>
|
|
<div>Provider quota management</div>
|
|
<div>Anti-blacklisting protection</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Bounce Handling & Recent Activity -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Bounce Handling -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-bounce-rate"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Bounce Handling</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showBounceModal = true"
|
|
>
|
|
View All
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
|
<div class="text-center p-3 bg-red-50 rounded-lg">
|
|
<div class="font-bold text-2xl text-red-600">{{ bounceStats.hard }}</div>
|
|
<div class="text-sm text-red-600">Hard Bounces</div>
|
|
</div>
|
|
<div class="text-center p-3 bg-yellow-50 rounded-lg">
|
|
<div class="font-bold text-2xl text-yellow-600">
|
|
{{ bounceStats.soft }}
|
|
</div>
|
|
<div class="text-sm text-yellow-600">Soft Bounces</div>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="bounce in recentBounces"
|
|
:key="bounce.id"
|
|
class="flex items-center justify-between p-2 border border-gray-200 rounded"
|
|
>
|
|
<div class="flex items-center">
|
|
<Icon
|
|
class="mr-2"
|
|
:name="
|
|
bounce.type === 'hard' ? 'ic:outline-error' : 'ic:outline-warning'
|
|
"
|
|
:class="bounce.type === 'hard' ? 'text-red-500' : 'text-yellow-500'"
|
|
></Icon>
|
|
<div>
|
|
<div class="font-medium text-sm">{{ bounce.recipient }}</div>
|
|
<div class="text-xs text-gray-500">{{ bounce.reason }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-xs text-gray-500">{{ bounce.time }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Recent Delivery Activity -->
|
|
<rs-card>
|
|
<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">Recent Activity</h2>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="activity in recentActivity"
|
|
:key="activity.id"
|
|
class="flex items-center p-3 border border-gray-200 rounded-lg"
|
|
>
|
|
<Icon
|
|
class="mr-3"
|
|
:name="activity.icon"
|
|
:class="activity.statusColor"
|
|
></Icon>
|
|
<div class="flex-1">
|
|
<div class="font-medium text-sm">{{ activity.message }}</div>
|
|
<div class="text-xs text-gray-500">
|
|
{{ activity.channel }} • {{ activity.time }}
|
|
</div>
|
|
</div>
|
|
<rs-badge :variant="activity.badgeVariant" size="sm">
|
|
{{ activity.status }}
|
|
</rs-badge>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Third-Party Integrations & Webhooks -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Third-Party Integrations -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-extension"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Third-Party Integrations</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showIntegrationsModal = true"
|
|
>
|
|
Manage
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="integration in integrations"
|
|
:key="integration.id"
|
|
class="flex items-center justify-between p-4 border border-gray-200 rounded-lg"
|
|
>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-3 text-2xl" :name="integration.icon"></Icon>
|
|
<div>
|
|
<div class="font-semibold">{{ integration.name }}</div>
|
|
<div class="text-sm text-gray-600">{{ integration.description }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<rs-badge
|
|
:variant="integration.status === 'connected' ? 'success' : 'danger'"
|
|
size="sm"
|
|
>
|
|
{{ integration.status }}
|
|
</rs-badge>
|
|
<div class="text-xs text-gray-500 mt-1">{{ integration.lastSync }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Webhook Configuration -->
|
|
<rs-card>
|
|
<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 Configuration</h2>
|
|
</div>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showWebhookModal = true"
|
|
>
|
|
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-center justify-between mb-2">
|
|
<div class="font-semibold">{{ webhook.name }}</div>
|
|
<rs-badge :variant="webhook.enabled ? 'success' : 'secondary'" size="sm">
|
|
{{ webhook.enabled ? "Active" : "Disabled" }}
|
|
</rs-badge>
|
|
</div>
|
|
<div class="text-sm text-gray-600 space-y-1">
|
|
<div>URL: {{ webhook.url }}</div>
|
|
<div>Events: {{ webhook.events.join(", ") }}</div>
|
|
<div>Last delivery: {{ webhook.lastDelivery }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Delivery Reports -->
|
|
<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-assessment"></Icon>
|
|
<h2 class="text-lg font-semibold text-primary">Delivery Reports</h2>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<rs-button size="sm" variant="primary-outline" @click="exportReports">
|
|
Export
|
|
</rs-button>
|
|
<rs-button
|
|
size="sm"
|
|
variant="primary-outline"
|
|
@click="showReportModal = true"
|
|
>
|
|
Generate Report
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<div class="text-center p-4 bg-blue-50 rounded-lg">
|
|
<div class="font-bold text-3xl text-blue-600">{{ reports.totalSent }}</div>
|
|
<div class="text-blue-600 font-medium">Total Messages Sent</div>
|
|
<div class="text-sm text-gray-500 mt-1">Last 24 hours</div>
|
|
</div>
|
|
<div class="text-center p-4 bg-green-50 rounded-lg">
|
|
<div class="font-bold text-3xl text-green-600">
|
|
{{ reports.successRate }}%
|
|
</div>
|
|
<div class="text-green-600 font-medium">Overall Success Rate</div>
|
|
<div class="text-sm text-gray-500 mt-1">7-day average</div>
|
|
</div>
|
|
<div class="text-center p-4 bg-purple-50 rounded-lg">
|
|
<div class="font-bold text-3xl text-purple-600">
|
|
{{ reports.avgDeliveryTime }}s
|
|
</div>
|
|
<div class="text-purple-600 font-medium">Avg Delivery Time</div>
|
|
<div class="text-sm text-gray-500 mt-1">End-to-end</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<h4 class="font-semibold mb-4">Channel Performance</h4>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="channelReport in channelReports"
|
|
:key="channelReport.channel"
|
|
class="flex items-center justify-between p-3 border border-gray-200 rounded-lg"
|
|
>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-3" :name="channelReport.icon"></Icon>
|
|
<span class="font-medium">{{ channelReport.channel }}</span>
|
|
</div>
|
|
<div class="flex gap-6 text-sm">
|
|
<div>
|
|
<span class="text-gray-600">Sent:</span>
|
|
<span class="ml-1 font-medium">{{ channelReport.sent }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600">Success:</span>
|
|
<span class="ml-1 font-medium">{{ channelReport.successRate }}%</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600">Avg Time:</span>
|
|
<span class="ml-1 font-medium">{{ channelReport.avgTime }}s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Modals would go here - Channel Config, Fallback Rules, etc. -->
|
|
<!-- For brevity, showing one example modal -->
|
|
|
|
<!-- Channel Configuration Modal -->
|
|
<rs-modal v-model="showChannelModal" size="lg">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold">Channel Configuration</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-6">
|
|
<div
|
|
v-for="channel in channels"
|
|
:key="channel.type"
|
|
class="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2" :name="channel.icon"></Icon>
|
|
<span class="font-semibold">{{ channel.name }}</span>
|
|
</div>
|
|
<FormKit type="toggle" v-model="channel.enabled" name="enabled" />
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<FormKit
|
|
type="select"
|
|
label="Primary Provider"
|
|
:value="channel.provider"
|
|
:options="channel.providerOptions"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<FormKit
|
|
type="select"
|
|
label="Fallback Provider"
|
|
:value="channel.fallbackProvider"
|
|
:options="channel.providerOptions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2">
|
|
<rs-button variant="outline" @click="showChannelModal = false"
|
|
>Cancel</rs-button
|
|
>
|
|
<rs-button variant="primary" @click="saveChannelConfig"
|
|
>Save Configuration</rs-button
|
|
>
|
|
</div>
|
|
</template>
|
|
</rs-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted } from "vue";
|
|
|
|
definePageMeta({
|
|
title: "Notification Delivery Engine",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{
|
|
name: "Dashboard",
|
|
path: "/dashboard",
|
|
},
|
|
{
|
|
name: "Notification",
|
|
path: "/notification",
|
|
},
|
|
{
|
|
name: "Delivery Engine",
|
|
path: "/notification/delivery",
|
|
type: "current",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Modal states
|
|
const showChannelModal = ref(false);
|
|
const showFallbackModal = ref(false);
|
|
const showOptimizationModal = ref(false);
|
|
const showBounceModal = ref(false);
|
|
const showIntegrationsModal = ref(false);
|
|
const showWebhookModal = ref(false);
|
|
const showReportModal = ref(false);
|
|
|
|
// Quick delivery stats
|
|
const deliveryStats = ref([
|
|
{
|
|
title: "Messages/Hour",
|
|
value: "15.2K",
|
|
icon: "ic:outline-speed",
|
|
},
|
|
{
|
|
title: "Success Rate",
|
|
value: "98.7%",
|
|
icon: "ic:outline-check-circle",
|
|
},
|
|
{
|
|
title: "Active Channels",
|
|
value: "4",
|
|
icon: "ic:outline-alt-route",
|
|
},
|
|
{
|
|
title: "Avg Delivery Time",
|
|
value: "1.2s",
|
|
icon: "ic:outline-timer",
|
|
},
|
|
]);
|
|
|
|
// Multi-channel delivery data
|
|
const channels = ref([
|
|
{
|
|
type: "email",
|
|
name: "Email",
|
|
icon: "ic:outline-email",
|
|
status: "active",
|
|
provider: "SendGrid",
|
|
fallbackProvider: "Mailgun",
|
|
successRate: 99.2,
|
|
enabled: true,
|
|
providerOptions: [
|
|
{ label: "SendGrid", value: "sendgrid" },
|
|
{ label: "Mailgun", value: "mailgun" },
|
|
{ label: "AWS SES", value: "ses" },
|
|
{ label: "Postmark", value: "postmark" },
|
|
],
|
|
},
|
|
{
|
|
type: "sms",
|
|
name: "SMS",
|
|
icon: "ic:outline-sms",
|
|
status: "active",
|
|
provider: "Twilio",
|
|
fallbackProvider: "Nexmo",
|
|
successRate: 97.8,
|
|
enabled: true,
|
|
providerOptions: [
|
|
{ label: "Twilio", value: "twilio" },
|
|
{ label: "Nexmo", value: "nexmo" },
|
|
{ label: "CM.com", value: "cm" },
|
|
],
|
|
},
|
|
{
|
|
type: "push",
|
|
name: "Push Notifications",
|
|
icon: "ic:outline-notifications",
|
|
status: "active",
|
|
provider: "FCM",
|
|
fallbackProvider: "APNs",
|
|
successRate: 95.4,
|
|
enabled: true,
|
|
providerOptions: [
|
|
{ label: "Firebase FCM", value: "fcm" },
|
|
{ label: "Apple APNs", value: "apns" },
|
|
{ label: "OneSignal", value: "onesignal" },
|
|
],
|
|
},
|
|
{
|
|
type: "inapp",
|
|
name: "In-App",
|
|
icon: "ic:outline-app-registration",
|
|
status: "active",
|
|
provider: "WebSocket",
|
|
fallbackProvider: "Polling",
|
|
successRate: 99.9,
|
|
enabled: true,
|
|
providerOptions: [
|
|
{ label: "WebSocket", value: "websocket" },
|
|
{ label: "Server-Sent Events", value: "sse" },
|
|
{ label: "Polling", value: "polling" },
|
|
],
|
|
},
|
|
]);
|
|
|
|
// Real-time delivery status tracking
|
|
const deliveryStatuses = ref([
|
|
{
|
|
stage: "Queued",
|
|
count: 1247,
|
|
percentage: 5.2,
|
|
icon: "ic:outline-queue",
|
|
color: "text-blue-500",
|
|
},
|
|
{
|
|
stage: "Sent",
|
|
count: 18563,
|
|
percentage: 77.8,
|
|
icon: "ic:outline-send",
|
|
color: "text-yellow-500",
|
|
},
|
|
{
|
|
stage: "Delivered",
|
|
count: 17821,
|
|
percentage: 74.7,
|
|
icon: "ic:outline-check-circle",
|
|
color: "text-green-500",
|
|
},
|
|
{
|
|
stage: "Opened",
|
|
count: 12456,
|
|
percentage: 52.2,
|
|
icon: "ic:outline-mark-email-read",
|
|
color: "text-purple-500",
|
|
},
|
|
{
|
|
stage: "Failed",
|
|
count: 324,
|
|
percentage: 1.4,
|
|
icon: "ic:outline-error",
|
|
color: "text-red-500",
|
|
},
|
|
{
|
|
stage: "Bounced",
|
|
count: 187,
|
|
percentage: 0.8,
|
|
icon: "ic:outline-reply",
|
|
color: "text-orange-500",
|
|
},
|
|
]);
|
|
|
|
// Fallback mechanism rules
|
|
const fallbackRules = ref([
|
|
{
|
|
id: 1,
|
|
name: "Email to SMS Fallback",
|
|
primary: "Email (SendGrid)",
|
|
fallback: "SMS (Twilio)",
|
|
trigger: "Hard bounce or 30s timeout",
|
|
enabled: true,
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Provider Failover",
|
|
primary: "SendGrid",
|
|
fallback: "Mailgun",
|
|
trigger: "API error or rate limit",
|
|
enabled: true,
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "Push to In-App",
|
|
primary: "Push Notification",
|
|
fallback: "In-App Notification",
|
|
trigger: "Device offline",
|
|
enabled: false,
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "SMS Regional Failover",
|
|
primary: "Twilio",
|
|
fallback: "Local Carrier",
|
|
trigger: "Region-specific delivery failure",
|
|
enabled: true,
|
|
},
|
|
]);
|
|
|
|
// Bounce handling statistics
|
|
const bounceStats = ref({
|
|
hard: 47,
|
|
soft: 128,
|
|
});
|
|
|
|
// Recent bounces
|
|
const recentBounces = ref([
|
|
{
|
|
id: 1,
|
|
recipient: "user@invalid.com",
|
|
type: "hard",
|
|
reason: "Domain not found",
|
|
time: "2 min ago",
|
|
},
|
|
{
|
|
id: 2,
|
|
recipient: "+1234567890",
|
|
type: "soft",
|
|
reason: "Temporary network issue",
|
|
time: "5 min ago",
|
|
},
|
|
{
|
|
id: 3,
|
|
recipient: "blocked@example.com",
|
|
type: "hard",
|
|
reason: "Recipient blocked sender",
|
|
time: "8 min ago",
|
|
},
|
|
]);
|
|
|
|
// Recent delivery activity
|
|
const recentActivity = ref([
|
|
{
|
|
id: 1,
|
|
message: "Bulk email campaign completed",
|
|
channel: "Email",
|
|
status: "Success",
|
|
badgeVariant: "success",
|
|
icon: "ic:outline-email",
|
|
statusColor: "text-green-500",
|
|
time: "1 min ago",
|
|
},
|
|
{
|
|
id: 2,
|
|
message: "SMS OTP batch processing",
|
|
channel: "SMS",
|
|
status: "Processing",
|
|
badgeVariant: "warning",
|
|
icon: "ic:outline-sms",
|
|
statusColor: "text-yellow-500",
|
|
time: "3 min ago",
|
|
},
|
|
{
|
|
id: 3,
|
|
message: "Push notification sent to 1.2K devices",
|
|
channel: "Push",
|
|
status: "Delivered",
|
|
badgeVariant: "success",
|
|
icon: "ic:outline-notifications",
|
|
statusColor: "text-green-500",
|
|
time: "5 min ago",
|
|
},
|
|
{
|
|
id: 4,
|
|
message: "Webhook delivery failed",
|
|
channel: "Webhook",
|
|
status: "Failed",
|
|
badgeVariant: "danger",
|
|
icon: "ic:outline-webhook",
|
|
statusColor: "text-red-500",
|
|
time: "7 min ago",
|
|
},
|
|
]);
|
|
|
|
// Third-party integrations
|
|
const integrations = ref([
|
|
{
|
|
id: 1,
|
|
name: "SendGrid",
|
|
description: "Email delivery service",
|
|
status: "connected",
|
|
icon: "ic:outline-email",
|
|
lastSync: "2 min ago",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Twilio",
|
|
description: "SMS & Voice API",
|
|
status: "connected",
|
|
icon: "ic:outline-sms",
|
|
lastSync: "5 min ago",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "Firebase FCM",
|
|
description: "Push notifications",
|
|
status: "connected",
|
|
icon: "ic:outline-notifications",
|
|
lastSync: "1 min ago",
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "Slack",
|
|
description: "Team notifications",
|
|
status: "disconnected",
|
|
icon: "ic:outline-chat",
|
|
lastSync: "Never",
|
|
},
|
|
]);
|
|
|
|
// Webhook configurations
|
|
const webhooks = ref([
|
|
{
|
|
id: 1,
|
|
name: "CRM Integration",
|
|
url: "https://api.crm.com/webhooks/delivery",
|
|
events: ["delivered", "opened", "bounced"],
|
|
enabled: true,
|
|
lastDelivery: "2 min ago",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Analytics Platform",
|
|
url: "https://analytics.example.com/webhook",
|
|
events: ["sent", "delivered", "failed"],
|
|
enabled: true,
|
|
lastDelivery: "5 min ago",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "Support System",
|
|
url: "https://support.example.com/notify",
|
|
events: ["failed", "bounced"],
|
|
enabled: false,
|
|
lastDelivery: "1 hour ago",
|
|
},
|
|
]);
|
|
|
|
// Delivery reports
|
|
const reports = ref({
|
|
totalSent: "245,678",
|
|
successRate: 98.7,
|
|
avgDeliveryTime: 1.2,
|
|
});
|
|
|
|
// Channel-specific reports
|
|
const channelReports = ref([
|
|
{
|
|
channel: "Email",
|
|
icon: "ic:outline-email",
|
|
sent: "156,234",
|
|
successRate: 99.2,
|
|
avgTime: 0.8,
|
|
},
|
|
{
|
|
channel: "SMS",
|
|
icon: "ic:outline-sms",
|
|
sent: "45,678",
|
|
successRate: 97.8,
|
|
avgTime: 2.1,
|
|
},
|
|
{
|
|
channel: "Push",
|
|
icon: "ic:outline-notifications",
|
|
sent: "89,123",
|
|
successRate: 95.4,
|
|
avgTime: 0.3,
|
|
},
|
|
{
|
|
channel: "In-App",
|
|
icon: "ic:outline-app-registration",
|
|
sent: "34,567",
|
|
successRate: 99.9,
|
|
avgTime: 0.1,
|
|
},
|
|
]);
|
|
|
|
// Methods
|
|
function refreshStatusData() {
|
|
// Simulate real-time data refresh
|
|
console.log("Refreshing delivery status data...");
|
|
// In real implementation, this would fetch latest data from API
|
|
}
|
|
|
|
function saveChannelConfig() {
|
|
// Save channel configuration
|
|
console.log("Saving channel configuration...", channels.value);
|
|
showChannelModal.value = false;
|
|
// Show success toast
|
|
}
|
|
|
|
function exportReports() {
|
|
// Export delivery reports
|
|
console.log("Exporting delivery reports...");
|
|
// In real implementation, this would generate and download reports
|
|
}
|
|
|
|
// Auto-refresh data every 30 seconds
|
|
let refreshInterval;
|
|
onMounted(() => {
|
|
refreshInterval = setInterval(refreshStatusData, 30000);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style>
|