- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
192 lines
7.0 KiB
TypeScript
192 lines
7.0 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
|
import { Send, CheckCircle, XCircle, Clock } from 'lucide-react';
|
|
|
|
export default function WebhookTestingPage() {
|
|
const [url, setUrl] = useState('');
|
|
const [method, setMethod] = useState('POST');
|
|
const [headers, setHeaders] = useState('{"Content-Type": "application/json"}');
|
|
const [payload, setPayload] = useState('{"event": "test", "data": {}}');
|
|
const [testResult, setTestResult] = useState<any>(null);
|
|
const [isTesting, setIsTesting] = useState(false);
|
|
|
|
const testWebhook = async () => {
|
|
setIsTesting(true);
|
|
setTestResult(null);
|
|
|
|
try {
|
|
const startTime = Date.now();
|
|
const response = await fetch('/api/webhooks/test', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
url,
|
|
method,
|
|
headers: JSON.parse(headers),
|
|
payload: JSON.parse(payload),
|
|
}),
|
|
});
|
|
const endTime = Date.now();
|
|
const data = await response.json();
|
|
|
|
setTestResult({
|
|
success: response.ok,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
responseTime: endTime - startTime,
|
|
response: data,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error: any) {
|
|
setTestResult({
|
|
success: false,
|
|
error: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} finally {
|
|
setIsTesting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-white mb-2">Webhook Testing Tool</h1>
|
|
<p className="text-gray-400">Test your webhook endpoints before configuring them</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<Card className="bg-gray-800 border-gray-700">
|
|
<CardHeader>
|
|
<CardTitle className="text-white">Webhook Configuration</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm text-gray-300 mb-2">Webhook URL</label>
|
|
<input
|
|
type="url"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
placeholder="https://example.com/webhook"
|
|
className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm text-gray-300 mb-2">HTTP Method</label>
|
|
<select
|
|
value={method}
|
|
onChange={(e) => setMethod(e.target.value)}
|
|
className="w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
|
|
>
|
|
<option value="POST">POST</option>
|
|
<option value="GET">GET</option>
|
|
<option value="PUT">PUT</option>
|
|
<option value="PATCH">PATCH</option>
|
|
<option value="DELETE">DELETE</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm text-gray-300 mb-2">Headers (JSON)</label>
|
|
<textarea
|
|
value={headers}
|
|
onChange={(e) => setHeaders(e.target.value)}
|
|
className="w-full h-24 px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white font-mono text-sm"
|
|
placeholder='{"Content-Type": "application/json"}'
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm text-gray-300 mb-2">Payload (JSON)</label>
|
|
<textarea
|
|
value={payload}
|
|
onChange={(e) => setPayload(e.target.value)}
|
|
className="w-full h-32 px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white font-mono text-sm"
|
|
placeholder='{"event": "test", "data": {}}'
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
onClick={testWebhook}
|
|
disabled={!url || isTesting}
|
|
className="w-full px-6 py-3 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
|
|
>
|
|
<Send className="h-4 w-4" />
|
|
{isTesting ? 'Testing...' : 'Test Webhook'}
|
|
</button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-gray-800 border-gray-700">
|
|
<CardHeader>
|
|
<CardTitle className="text-white">Test Results</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!testResult ? (
|
|
<div className="text-center py-12 text-gray-400">
|
|
<p>Test results will appear here</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-2">
|
|
{testResult.success ? (
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<XCircle className="h-5 w-5 text-red-400" />
|
|
)}
|
|
<span className={`font-semibold ${testResult.success ? 'text-green-400' : 'text-red-400'}`}>
|
|
{testResult.success ? 'Success' : 'Failed'}
|
|
</span>
|
|
</div>
|
|
|
|
{testResult.status && (
|
|
<div>
|
|
<p className="text-sm text-gray-400 mb-1">Status</p>
|
|
<p className="text-white font-mono">
|
|
{testResult.status} {testResult.statusText}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{testResult.responseTime && (
|
|
<div>
|
|
<p className="text-sm text-gray-400 mb-1">Response Time</p>
|
|
<p className="text-white font-mono">{testResult.responseTime}ms</p>
|
|
</div>
|
|
)}
|
|
|
|
{testResult.timestamp && (
|
|
<div>
|
|
<p className="text-sm text-gray-400 mb-1">Timestamp</p>
|
|
<p className="text-white text-sm">{new Date(testResult.timestamp).toLocaleString()}</p>
|
|
</div>
|
|
)}
|
|
|
|
{testResult.response && (
|
|
<div>
|
|
<p className="text-sm text-gray-400 mb-1">Response</p>
|
|
<pre className="p-3 bg-gray-900 rounded text-white font-mono text-xs overflow-auto max-h-64">
|
|
{JSON.stringify(testResult.response, null, 2)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{testResult.error && (
|
|
<div>
|
|
<p className="text-sm text-gray-400 mb-1">Error</p>
|
|
<p className="text-red-400 font-mono text-sm">{testResult.error}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|