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>