768 lines
25 KiB
Vue
768 lines
25 KiB
Vue
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Header Section -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" name="ic:outline-speed"></Icon>
|
|
<h1 class="text-xl font-bold text-primary">Rate Limiting</h1>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<p class="text-gray-600">
|
|
Throttles how many messages/jobs can be processed per second/minute/hour.
|
|
Avoid hitting API limits (Twilio, SendGrid) and prevent spammy behavior that can trigger blacklisting.
|
|
</p>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Rate Limit 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 rateLimitStats"
|
|
: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-4 flex justify-center items-center rounded-2xl"
|
|
:class="stat.bgColor"
|
|
>
|
|
<Icon class="text-2xl" :class="stat.iconColor" :name="stat.icon"></Icon>
|
|
</div>
|
|
<div class="flex-1 truncate">
|
|
<span class="block font-bold text-xl 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>
|
|
|
|
<!-- Current Usage Overview -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-primary">Current Usage Overview</h3>
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex items-center">
|
|
<div class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></div>
|
|
<span class="text-sm text-gray-600">Live Updates</span>
|
|
</div>
|
|
<rs-button variant="outline" size="sm" @click="refreshUsage">
|
|
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
|
|
Refresh
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div
|
|
v-for="(usage, index) in currentUsage"
|
|
:key="index"
|
|
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="usage.icon"></Icon>
|
|
<h4 class="font-semibold">{{ usage.service }}</h4>
|
|
</div>
|
|
<rs-badge :variant="getUsageVariant(usage.percentage)">
|
|
{{ usage.percentage }}%
|
|
</rs-badge>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Current Rate:</span>
|
|
<span class="font-medium">{{ usage.currentRate }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Limit:</span>
|
|
<span class="font-medium">{{ usage.limit }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">Window:</span>
|
|
<span class="font-medium">{{ usage.window }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<rs-progress-bar
|
|
:value="usage.percentage"
|
|
:variant="usage.percentage > 80 ? 'danger' : usage.percentage > 60 ? 'warning' : 'success'"
|
|
/>
|
|
</div>
|
|
|
|
<div class="mt-2 text-xs text-gray-500">
|
|
Resets in {{ usage.resetTime }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Rate Limit Configuration -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-primary">Rate Limit Configuration</h3>
|
|
<rs-button variant="outline" size="sm" @click="showConfigModal = true">
|
|
<Icon class="mr-1" name="ic:outline-settings"></Icon>
|
|
Configure Limits
|
|
</rs-button>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<rs-table
|
|
:field="configTableFields"
|
|
:data="rateLimitConfigs"
|
|
:options="{ striped: true, hover: true }"
|
|
:optionsAdvanced="{ sortable: true, filterable: false }"
|
|
advanced
|
|
/>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Rate Limit Violations -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-primary">Recent Rate Limit Violations</h3>
|
|
<div class="flex items-center gap-2">
|
|
<select v-model="violationFilter" class="p-2 border border-gray-300 rounded-md text-sm">
|
|
<option value="">All Services</option>
|
|
<option value="email">Email</option>
|
|
<option value="sms">SMS</option>
|
|
<option value="push">Push</option>
|
|
<option value="webhook">Webhook</option>
|
|
</select>
|
|
<rs-button variant="outline" size="sm" @click="refreshViolations">
|
|
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
|
|
Refresh
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div v-if="filteredViolations.length === 0" class="text-center py-8 text-gray-500">
|
|
<Icon class="text-4xl mb-2" name="ic:outline-check-circle"></Icon>
|
|
<p>No rate limit violations in the selected timeframe</p>
|
|
</div>
|
|
<div v-else class="space-y-3">
|
|
<div
|
|
v-for="(violation, index) in filteredViolations"
|
|
:key="index"
|
|
class="border border-red-200 bg-red-50 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-red-600" name="ic:outline-warning"></Icon>
|
|
<span class="font-semibold text-red-800">{{ violation.service }} Rate Limit Exceeded</span>
|
|
</div>
|
|
<span class="text-sm text-red-600">{{ violation.timestamp }}</span>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-red-700">Attempted Rate:</span>
|
|
<span class="ml-1 font-medium">{{ violation.attemptedRate }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-red-700">Limit:</span>
|
|
<span class="ml-1 font-medium">{{ violation.limit }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-red-700">Messages Dropped:</span>
|
|
<span class="ml-1 font-medium">{{ violation.droppedMessages }}</span>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-red-700 mt-2">{{ violation.description }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Rate Limit Analytics -->
|
|
<rs-card class="mb-6">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold text-primary">Rate Limit Analytics</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<!-- Usage Trends -->
|
|
<div>
|
|
<h4 class="font-semibold mb-4">Usage Trends (Last 24 Hours)</h4>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="(trend, index) in usageTrends"
|
|
:key="index"
|
|
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
|
>
|
|
<div class="flex items-center">
|
|
<Icon class="mr-2 text-primary" :name="trend.icon"></Icon>
|
|
<div>
|
|
<p class="font-medium">{{ trend.service }}</p>
|
|
<p class="text-sm text-gray-600">Peak: {{ trend.peak }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-medium">{{ trend.average }}</p>
|
|
<p class="text-sm text-gray-600">Average</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Efficiency Metrics -->
|
|
<div>
|
|
<h4 class="font-semibold mb-4">Efficiency Metrics</h4>
|
|
<div class="space-y-4">
|
|
<div class="bg-green-50 border border-green-200 rounded p-3">
|
|
<div class="flex items-center mb-2">
|
|
<Icon class="mr-2 text-green-600" name="ic:outline-trending-up"></Icon>
|
|
<span class="font-medium text-green-800">Throughput Optimization</span>
|
|
</div>
|
|
<p class="text-sm text-green-700">
|
|
Current efficiency: {{ efficiencyMetrics.throughputOptimization }}%
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-blue-50 border border-blue-200 rounded p-3">
|
|
<div class="flex items-center mb-2">
|
|
<Icon class="mr-2 text-blue-600" name="ic:outline-schedule"></Icon>
|
|
<span class="font-medium text-blue-800">Queue Utilization</span>
|
|
</div>
|
|
<p class="text-sm text-blue-700">
|
|
Average queue utilization: {{ efficiencyMetrics.queueUtilization }}%
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-purple-50 border border-purple-200 rounded p-3">
|
|
<div class="flex items-center mb-2">
|
|
<Icon class="mr-2 text-purple-600" name="ic:outline-timer"></Icon>
|
|
<span class="font-medium text-purple-800">Response Time</span>
|
|
</div>
|
|
<p class="text-sm text-purple-700">
|
|
Average response time: {{ efficiencyMetrics.responseTime }}ms
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-orange-50 border border-orange-200 rounded p-3">
|
|
<div class="flex items-center mb-2">
|
|
<Icon class="mr-2 text-orange-600" name="ic:outline-error"></Icon>
|
|
<span class="font-medium text-orange-800">Error Rate</span>
|
|
</div>
|
|
<p class="text-sm text-orange-700">
|
|
Rate limit errors: {{ efficiencyMetrics.errorRate }}%
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Rate Limit Testing -->
|
|
<rs-card>
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold text-primary">Rate Limit Testing</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Service to Test</label>
|
|
<select v-model="testConfig.service" class="w-full p-2 border border-gray-300 rounded-md">
|
|
<option value="">Select service</option>
|
|
<option value="email">Email (SendGrid)</option>
|
|
<option value="sms">SMS (Twilio)</option>
|
|
<option value="push">Push (Firebase)</option>
|
|
<option value="webhook">Webhook</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Messages per Second</label>
|
|
<input
|
|
v-model.number="testConfig.rate"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
placeholder="10"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Duration (seconds)</label>
|
|
<input
|
|
v-model.number="testConfig.duration"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
placeholder="60"
|
|
min="1"
|
|
max="300"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Test Message</label>
|
|
<textarea
|
|
v-model="testConfig.message"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
rows="3"
|
|
placeholder="Test message content"
|
|
></textarea>
|
|
</div>
|
|
|
|
<rs-button
|
|
@click="startRateLimitTest"
|
|
variant="primary"
|
|
class="w-full"
|
|
:disabled="testRunning"
|
|
>
|
|
<Icon class="mr-1" :name="testRunning ? 'ic:outline-stop' : 'ic:outline-play-arrow'"></Icon>
|
|
{{ testRunning ? 'Test Running...' : 'Start Rate Limit Test' }}
|
|
</rs-button>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<h4 class="font-semibold">Test Results</h4>
|
|
<div v-if="testResults.length === 0" class="text-center text-gray-500 py-8">
|
|
<Icon class="text-4xl mb-2" name="ic:outline-science"></Icon>
|
|
<p>Run a test to see results</p>
|
|
</div>
|
|
<div v-else class="space-y-3 max-h-60 overflow-y-auto">
|
|
<div
|
|
v-for="(result, index) in testResults"
|
|
:key="index"
|
|
class="border border-gray-200 rounded p-3"
|
|
>
|
|
<div class="flex justify-between items-start mb-2">
|
|
<span class="font-medium">{{ result.service }}</span>
|
|
<span :class="{
|
|
'text-green-600': result.status === 'success',
|
|
'text-red-600': result.status === 'rate_limited',
|
|
'text-yellow-600': result.status === 'warning'
|
|
}" class="text-sm font-medium">{{ result.status }}</span>
|
|
</div>
|
|
<div class="text-sm space-y-1">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-600">Messages Sent:</span>
|
|
<span>{{ result.messagesSent }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-600">Rate Achieved:</span>
|
|
<span>{{ result.rateAchieved }}/sec</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-600">Errors:</span>
|
|
<span>{{ result.errors }}</span>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">{{ result.timestamp }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Configuration Modal -->
|
|
<rs-modal v-model="showConfigModal" size="lg">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold">Rate Limit Configuration</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-6">
|
|
<div
|
|
v-for="(config, index) in editableConfigs"
|
|
:key="index"
|
|
class="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center mb-4">
|
|
<Icon class="mr-2 text-primary" :name="config.icon"></Icon>
|
|
<h4 class="font-semibold">{{ config.service }} Configuration</h4>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Messages per Second</label>
|
|
<input
|
|
v-model.number="config.perSecond"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Messages per Minute</label>
|
|
<input
|
|
v-model.number="config.perMinute"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Messages per Hour</label>
|
|
<input
|
|
v-model.number="config.perHour"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Burst Limit</label>
|
|
<input
|
|
v-model.number="config.burstLimit"
|
|
type="number"
|
|
class="w-full p-2 border border-gray-300 rounded-md"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<label class="flex items-center">
|
|
<input
|
|
v-model="config.enabled"
|
|
type="checkbox"
|
|
class="mr-2"
|
|
/>
|
|
<span class="text-sm font-medium text-gray-700">Enable rate limiting for this service</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2">
|
|
<rs-button variant="outline" @click="showConfigModal = false">Cancel</rs-button>
|
|
<rs-button @click="saveRateLimitConfig" variant="primary">Save Configuration</rs-button>
|
|
</div>
|
|
</template>
|
|
</rs-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
definePageMeta({
|
|
title: "Rate Limiting",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{
|
|
name: "Notification",
|
|
path: "/notification",
|
|
},
|
|
{
|
|
name: "Queue & Scheduler",
|
|
path: "/notification/queue-scheduler",
|
|
},
|
|
{
|
|
name: "Rate Limiting",
|
|
path: "/notification/queue-scheduler/rate-limit",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Reactive data
|
|
const showConfigModal = ref(false);
|
|
const violationFilter = ref('');
|
|
const testRunning = ref(false);
|
|
const testResults = ref([]);
|
|
|
|
// Statistics
|
|
const rateLimitStats = ref([
|
|
{
|
|
title: "Active Limits",
|
|
value: "12",
|
|
icon: "ic:outline-speed",
|
|
bgColor: "bg-blue-100",
|
|
iconColor: "text-blue-600",
|
|
textColor: "text-blue-600"
|
|
},
|
|
{
|
|
title: "Messages/Hour",
|
|
value: "45.2K",
|
|
icon: "ic:outline-trending-up",
|
|
bgColor: "bg-green-100",
|
|
iconColor: "text-green-600",
|
|
textColor: "text-green-600"
|
|
},
|
|
{
|
|
title: "Violations Today",
|
|
value: "3",
|
|
icon: "ic:outline-warning",
|
|
bgColor: "bg-red-100",
|
|
iconColor: "text-red-600",
|
|
textColor: "text-red-600"
|
|
},
|
|
{
|
|
title: "Efficiency",
|
|
value: "96.8%",
|
|
icon: "ic:outline-check-circle",
|
|
bgColor: "bg-purple-100",
|
|
iconColor: "text-purple-600",
|
|
textColor: "text-purple-600"
|
|
}
|
|
]);
|
|
|
|
// Current usage
|
|
const currentUsage = ref([
|
|
{
|
|
service: 'Email (SendGrid)',
|
|
icon: 'ic:outline-email',
|
|
currentRate: '850/hour',
|
|
limit: '1000/hour',
|
|
percentage: 85,
|
|
window: '1 hour',
|
|
resetTime: '23 minutes'
|
|
},
|
|
{
|
|
service: 'SMS (Twilio)',
|
|
icon: 'ic:outline-sms',
|
|
currentRate: '45/minute',
|
|
limit: '100/minute',
|
|
percentage: 45,
|
|
window: '1 minute',
|
|
resetTime: '32 seconds'
|
|
},
|
|
{
|
|
service: 'Push (Firebase)',
|
|
icon: 'ic:outline-notifications',
|
|
currentRate: '1200/hour',
|
|
limit: '5000/hour',
|
|
percentage: 24,
|
|
window: '1 hour',
|
|
resetTime: '45 minutes'
|
|
},
|
|
{
|
|
service: 'Webhook',
|
|
icon: 'ic:outline-webhook',
|
|
currentRate: '15/second',
|
|
limit: '20/second',
|
|
percentage: 75,
|
|
window: '1 second',
|
|
resetTime: '0.5 seconds'
|
|
}
|
|
]);
|
|
|
|
// Configuration table fields
|
|
const configTableFields = ref([
|
|
{ key: 'service', label: 'Service', sortable: true },
|
|
{ key: 'perSecond', label: 'Per Second', sortable: true },
|
|
{ key: 'perMinute', label: 'Per Minute', sortable: true },
|
|
{ key: 'perHour', label: 'Per Hour', sortable: true },
|
|
{ key: 'burstLimit', label: 'Burst Limit', sortable: true },
|
|
{ key: 'status', label: 'Status', sortable: true },
|
|
{ key: 'actions', label: 'Actions', sortable: false }
|
|
]);
|
|
|
|
// Rate limit configurations
|
|
const rateLimitConfigs = ref([
|
|
{
|
|
service: 'Email (SendGrid)',
|
|
icon: 'ic:outline-email',
|
|
perSecond: 10,
|
|
perMinute: 600,
|
|
perHour: 1000,
|
|
burstLimit: 50,
|
|
enabled: true
|
|
},
|
|
{
|
|
service: 'SMS (Twilio)',
|
|
icon: 'ic:outline-sms',
|
|
perSecond: 5,
|
|
perMinute: 100,
|
|
perHour: 2000,
|
|
burstLimit: 20,
|
|
enabled: true
|
|
},
|
|
{
|
|
service: 'Push (Firebase)',
|
|
icon: 'ic:outline-notifications',
|
|
perSecond: 50,
|
|
perMinute: 1000,
|
|
perHour: 5000,
|
|
burstLimit: 200,
|
|
enabled: true
|
|
},
|
|
{
|
|
service: 'Webhook',
|
|
icon: 'ic:outline-webhook',
|
|
perSecond: 20,
|
|
perMinute: 500,
|
|
perHour: 10000,
|
|
burstLimit: 100,
|
|
enabled: true
|
|
}
|
|
].map(config => ({
|
|
...config,
|
|
status: h('span', {
|
|
class: `px-2 py-1 rounded text-xs font-medium ${
|
|
config.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
|
}`
|
|
}, config.enabled ? 'Active' : 'Disabled'),
|
|
actions: h('button', {
|
|
class: 'text-blue-600 hover:text-blue-800 text-sm',
|
|
onClick: () => editRateLimit(config)
|
|
}, 'Edit')
|
|
})));
|
|
|
|
// Editable configs for modal
|
|
const editableConfigs = ref(JSON.parse(JSON.stringify(rateLimitConfigs.value.map(c => ({
|
|
service: c.service,
|
|
icon: c.icon,
|
|
perSecond: c.perSecond,
|
|
perMinute: c.perMinute,
|
|
perHour: c.perHour,
|
|
burstLimit: c.burstLimit,
|
|
enabled: c.enabled
|
|
})))));
|
|
|
|
// Rate limit violations
|
|
const violations = ref([
|
|
{
|
|
service: 'Email',
|
|
timestamp: '2024-01-15 14:30:00',
|
|
attemptedRate: '1200/hour',
|
|
limit: '1000/hour',
|
|
droppedMessages: 45,
|
|
description: 'Newsletter campaign exceeded hourly limit during peak hours'
|
|
},
|
|
{
|
|
service: 'SMS',
|
|
timestamp: '2024-01-15 12:15:00',
|
|
attemptedRate: '150/minute',
|
|
limit: '100/minute',
|
|
droppedMessages: 23,
|
|
description: 'OTP verification burst exceeded per-minute limit'
|
|
},
|
|
{
|
|
service: 'Webhook',
|
|
timestamp: '2024-01-15 10:45:00',
|
|
attemptedRate: '25/second',
|
|
limit: '20/second',
|
|
droppedMessages: 12,
|
|
description: 'Order webhook notifications exceeded per-second limit'
|
|
}
|
|
]);
|
|
|
|
// Usage trends
|
|
const usageTrends = ref([
|
|
{
|
|
service: 'Email',
|
|
icon: 'ic:outline-email',
|
|
peak: '950/hour',
|
|
average: '650/hour'
|
|
},
|
|
{
|
|
service: 'SMS',
|
|
icon: 'ic:outline-sms',
|
|
peak: '85/minute',
|
|
average: '45/minute'
|
|
},
|
|
{
|
|
service: 'Push',
|
|
icon: 'ic:outline-notifications',
|
|
peak: '2100/hour',
|
|
average: '1200/hour'
|
|
},
|
|
{
|
|
service: 'Webhook',
|
|
icon: 'ic:outline-webhook',
|
|
peak: '18/second',
|
|
average: '12/second'
|
|
}
|
|
]);
|
|
|
|
// Efficiency metrics
|
|
const efficiencyMetrics = ref({
|
|
throughputOptimization: 96.8,
|
|
queueUtilization: 78.5,
|
|
responseTime: 245,
|
|
errorRate: 0.3
|
|
});
|
|
|
|
// Test configuration
|
|
const testConfig = ref({
|
|
service: '',
|
|
rate: 10,
|
|
duration: 60,
|
|
message: 'This is a rate limit test message'
|
|
});
|
|
|
|
// Computed filtered violations
|
|
const filteredViolations = computed(() => {
|
|
if (!violationFilter.value) {
|
|
return violations.value;
|
|
}
|
|
return violations.value.filter(v => v.service.toLowerCase() === violationFilter.value);
|
|
});
|
|
|
|
// Methods
|
|
function getUsageVariant(percentage) {
|
|
if (percentage > 80) return 'danger';
|
|
if (percentage > 60) return 'warning';
|
|
return 'success';
|
|
}
|
|
|
|
function refreshUsage() {
|
|
// Mock refresh
|
|
console.log('Refreshing usage data...');
|
|
}
|
|
|
|
function refreshViolations() {
|
|
// Mock refresh
|
|
console.log('Refreshing violations...');
|
|
}
|
|
|
|
function editRateLimit(config) {
|
|
// Find and update the editable config
|
|
const editableConfig = editableConfigs.value.find(c => c.service === config.service);
|
|
if (editableConfig) {
|
|
Object.assign(editableConfig, config);
|
|
}
|
|
showConfigModal.value = true;
|
|
}
|
|
|
|
function saveRateLimitConfig() {
|
|
// Mock save
|
|
console.log('Saving rate limit configuration...', editableConfigs.value);
|
|
showConfigModal.value = false;
|
|
}
|
|
|
|
function startRateLimitTest() {
|
|
if (!testConfig.value.service) {
|
|
return;
|
|
}
|
|
|
|
testRunning.value = true;
|
|
|
|
// Mock test execution
|
|
setTimeout(() => {
|
|
const result = {
|
|
service: testConfig.value.service,
|
|
messagesSent: Math.floor(testConfig.value.rate * testConfig.value.duration * 0.9),
|
|
rateAchieved: Math.floor(testConfig.value.rate * 0.9),
|
|
errors: Math.floor(Math.random() * 5),
|
|
status: Math.random() > 0.7 ? 'rate_limited' : 'success',
|
|
timestamp: new Date().toLocaleString()
|
|
};
|
|
|
|
testResults.value.unshift(result);
|
|
testRunning.value = false;
|
|
}, 3000);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped></style> |