completed SMS gateway project
This commit is contained in:
466
app/views/admin/api_tester/index.html.erb
Normal file
466
app/views/admin/api_tester/index.html.erb
Normal file
@@ -0,0 +1,466 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Page header -->
|
||||
<div class="border-b border-gray-200 pb-5">
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">API Tester</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Test all API endpoints with interactive forms. View request/response in real-time.</p>
|
||||
</div>
|
||||
|
||||
<!-- API Key Selection Card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Authentication</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Available API Keys (for reference) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Available API Keys (Reference Only)</label>
|
||||
<div class="rounded-lg bg-gray-50 p-4 space-y-2">
|
||||
<% if @api_keys.any? %>
|
||||
<% @api_keys.each do |key| %>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-700">
|
||||
<i class="fas fa-key text-blue-500"></i> <%= key.name %>
|
||||
</span>
|
||||
<span class="font-mono text-xs text-gray-500"><%= key.key_prefix %>***</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Raw API keys are only shown once during creation for security. Enter your saved API key below.
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No API keys found. Create one first.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enter API Key -->
|
||||
<div>
|
||||
<label for="api-key-input" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Enter API Key <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="api-key-input"
|
||||
class="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono text-xs"
|
||||
placeholder="api_live_... or gw_live_..."
|
||||
required>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
Client API keys start with <code class="bg-gray-100 px-1 py-0.5 rounded">api_live_</code>,
|
||||
Gateway keys start with <code class="bg-gray-100 px-1 py-0.5 rounded">gw_live_</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Base URL -->
|
||||
<div>
|
||||
<label for="base-url" class="block text-sm font-medium text-gray-700 mb-2">Base URL</label>
|
||||
<input
|
||||
type="text"
|
||||
id="base-url"
|
||||
class="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono text-xs"
|
||||
value="<%= request.base_url %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint Testing Tabs -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5">
|
||||
<!-- Tab Navigation -->
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="flex gap-4 px-6 pt-6" aria-label="Tabs">
|
||||
<button onclick="switchTab('send-sms')" id="tab-send-sms" class="tab-button active">
|
||||
<i class="fas fa-paper-plane"></i> Send SMS
|
||||
</button>
|
||||
<button onclick="switchTab('check-status')" id="tab-check-status" class="tab-button">
|
||||
<i class="fas fa-info-circle"></i> Check Status
|
||||
</button>
|
||||
<button onclick="switchTab('send-otp')" id="tab-send-otp" class="tab-button">
|
||||
<i class="fas fa-key"></i> Send OTP
|
||||
</button>
|
||||
<button onclick="switchTab('verify-otp')" id="tab-verify-otp" class="tab-button">
|
||||
<i class="fas fa-check-circle"></i> Verify OTP
|
||||
</button>
|
||||
<button onclick="switchTab('gateway-register')" id="tab-gateway-register" class="tab-button">
|
||||
<i class="fas fa-mobile-alt"></i> Register Gateway
|
||||
</button>
|
||||
<button onclick="switchTab('gateway-heartbeat')" id="tab-gateway-heartbeat" class="tab-button">
|
||||
<i class="fas fa-heartbeat"></i> Heartbeat
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="px-6 py-6">
|
||||
<!-- Send SMS Tab -->
|
||||
<div id="content-send-sms" class="tab-content active">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Send SMS</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">POST /api/v1/sms/send</p>
|
||||
|
||||
<form id="form-send-sms" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Phone Number</label>
|
||||
<input type="tel" name="phone_number" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="+959123456789" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Message</label>
|
||||
<textarea name="message" rows="3" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="Your message here" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-paper-plane"></i> Send SMS
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Check Status Tab -->
|
||||
<div id="content-check-status" class="tab-content">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Check SMS Status</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">GET /api/v1/sms/status/:message_id</p>
|
||||
|
||||
<form id="form-check-status" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Message ID</label>
|
||||
<input type="text" name="message_id" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono text-xs" placeholder="msg_abc123..." required>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-search"></i> Check Status
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Send OTP Tab -->
|
||||
<div id="content-send-otp" class="tab-content">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Send OTP</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">POST /api/v1/otp/send</p>
|
||||
|
||||
<form id="form-send-otp" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Phone Number</label>
|
||||
<input type="tel" name="phone_number" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="+959123456789" required>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-key"></i> Send OTP
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Verify OTP Tab -->
|
||||
<div id="content-verify-otp" class="tab-content">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Verify OTP</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">POST /api/v1/otp/verify</p>
|
||||
|
||||
<form id="form-verify-otp" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Phone Number</label>
|
||||
<input type="tel" name="phone_number" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="+959123456789" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">OTP Code</label>
|
||||
<input type="text" name="code" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="123456" maxlength="6" required>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-check-circle"></i> Verify OTP
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Gateway Register Tab -->
|
||||
<div id="content-gateway-register" class="tab-content">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Register Gateway</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">POST /api/v1/gateway/register</p>
|
||||
|
||||
<form id="form-gateway-register" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Device ID</label>
|
||||
<input type="text" name="device_id" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="android-001" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Device Name</label>
|
||||
<input type="text" name="name" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="My Phone" required>
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-mobile-alt"></i> Register Gateway
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Gateway Heartbeat Tab -->
|
||||
<div id="content-gateway-heartbeat" class="tab-content">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Send Heartbeat</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">POST /api/v1/gateway/heartbeat</p>
|
||||
|
||||
<form id="form-gateway-heartbeat" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Battery Level (%)</label>
|
||||
<input type="number" name="battery_level" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="85" min="0" max="100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Signal Strength (0-4)</label>
|
||||
<input type="number" name="signal_strength" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" placeholder="4" min="0" max="4">
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
|
||||
<i class="fas fa-heartbeat"></i> Send Heartbeat
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Request/Response Display -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Request -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Request</h3>
|
||||
<button onclick="copyRequest()" class="text-sm text-blue-600 hover:text-blue-500">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre id="request-display" class="bg-gray-50 rounded-lg p-4 text-xs font-mono text-gray-800 overflow-x-auto max-h-96 overflow-y-auto"><span class="text-gray-400">Request will appear here...</span></pre>
|
||||
</div>
|
||||
|
||||
<!-- Response -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Response</h3>
|
||||
<button onclick="copyResponse()" class="text-sm text-blue-600 hover:text-blue-500">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre id="response-display" class="bg-gray-50 rounded-lg p-4 text-xs font-mono text-gray-800 overflow-x-auto max-h-96 overflow-y-auto"><span class="text-gray-400">Response will appear here...</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Card -->
|
||||
<div class="rounded-lg bg-blue-50 px-4 py-4 ring-1 ring-blue-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-info-circle text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-blue-800">API Testing Tips</h3>
|
||||
<ul class="mt-2 text-sm text-blue-700 list-disc list-inside space-y-1">
|
||||
<li>Select an API key from the dropdown or enter a custom key</li>
|
||||
<li>All requests use the Authorization header with Bearer token</li>
|
||||
<li>Gateway endpoints require gateway API keys (gw_live_...)</li>
|
||||
<li>Client endpoints require client API keys (api_live_...)</li>
|
||||
<li>View full request and response in real-time</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #2563eb;
|
||||
border-bottom-color: #2563eb;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let currentRequest = '';
|
||||
let currentResponse = '';
|
||||
|
||||
// Tab switching
|
||||
function switchTab(tabName) {
|
||||
// Hide all tab contents
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// Deactivate all tab buttons
|
||||
document.querySelectorAll('.tab-button').forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show selected tab content
|
||||
document.getElementById('content-' + tabName).classList.add('active');
|
||||
document.getElementById('tab-' + tabName).classList.add('active');
|
||||
}
|
||||
|
||||
// Get API key
|
||||
function getApiKey() {
|
||||
const apiKey = document.getElementById('api-key-input').value.trim();
|
||||
if (!apiKey) {
|
||||
alert('Please enter an API key');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate format
|
||||
if (!apiKey.startsWith('api_live_') && !apiKey.startsWith('gw_live_')) {
|
||||
alert('Invalid API key format. Keys should start with "api_live_" or "gw_live_"');
|
||||
return null;
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// Get base URL
|
||||
function getBaseUrl() {
|
||||
return document.getElementById('base-url').value.trim();
|
||||
}
|
||||
|
||||
// Display request
|
||||
function displayRequest(method, url, headers, body) {
|
||||
let request = `${method} ${url}\n\n`;
|
||||
|
||||
request += 'Headers:\n';
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
request += `${key}: ${value}\n`;
|
||||
});
|
||||
|
||||
if (body) {
|
||||
request += '\nBody:\n';
|
||||
request += JSON.stringify(body, null, 2);
|
||||
}
|
||||
|
||||
currentRequest = request;
|
||||
document.getElementById('request-display').textContent = request;
|
||||
}
|
||||
|
||||
// Display response
|
||||
function displayResponse(status, statusText, data) {
|
||||
let response = `Status: ${status} ${statusText}\n\n`;
|
||||
response += JSON.stringify(data, null, 2);
|
||||
|
||||
currentResponse = response;
|
||||
const displayElement = document.getElementById('response-display');
|
||||
displayElement.textContent = response;
|
||||
|
||||
// Color code based on status
|
||||
if (status >= 200 && status < 300) {
|
||||
displayElement.classList.add('text-green-700');
|
||||
displayElement.classList.remove('text-red-700', 'text-gray-800');
|
||||
} else {
|
||||
displayElement.classList.add('text-red-700');
|
||||
displayElement.classList.remove('text-green-700', 'text-gray-800');
|
||||
}
|
||||
}
|
||||
|
||||
// Copy functions
|
||||
function copyRequest() {
|
||||
navigator.clipboard.writeText(currentRequest);
|
||||
alert('Request copied to clipboard!');
|
||||
}
|
||||
|
||||
function copyResponse() {
|
||||
navigator.clipboard.writeText(currentResponse);
|
||||
alert('Response copied to clipboard!');
|
||||
}
|
||||
|
||||
// Generic API call
|
||||
async function makeApiCall(method, endpoint, body = null) {
|
||||
const apiKey = getApiKey();
|
||||
if (!apiKey) return;
|
||||
|
||||
const baseUrl = getBaseUrl();
|
||||
const url = `${baseUrl}${endpoint}`;
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
displayRequest(method, endpoint, headers, body);
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
const data = await response.json();
|
||||
|
||||
displayResponse(response.status, response.statusText, data);
|
||||
} catch (error) {
|
||||
displayResponse(0, 'Error', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Form handlers
|
||||
document.getElementById('form-send-sms').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const body = {
|
||||
to: formData.get('phone_number'), // API expects 'to' parameter
|
||||
message: formData.get('message')
|
||||
};
|
||||
await makeApiCall('POST', '/api/v1/sms/send', body);
|
||||
});
|
||||
|
||||
document.getElementById('form-check-status').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const messageId = formData.get('message_id');
|
||||
await makeApiCall('GET', `/api/v1/sms/status/${messageId}`);
|
||||
});
|
||||
|
||||
document.getElementById('form-send-otp').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const body = {
|
||||
phone_number: formData.get('phone_number')
|
||||
};
|
||||
await makeApiCall('POST', '/api/v1/otp/send', body);
|
||||
});
|
||||
|
||||
document.getElementById('form-verify-otp').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const body = {
|
||||
phone_number: formData.get('phone_number'),
|
||||
code: formData.get('code')
|
||||
};
|
||||
await makeApiCall('POST', '/api/v1/otp/verify', body);
|
||||
});
|
||||
|
||||
document.getElementById('form-gateway-register').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const body = {
|
||||
device_id: formData.get('device_id'),
|
||||
name: formData.get('name')
|
||||
};
|
||||
await makeApiCall('POST', '/api/v1/gateway/register', body);
|
||||
});
|
||||
|
||||
document.getElementById('form-gateway-heartbeat').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const body = {
|
||||
device_info: {
|
||||
battery_level: parseInt(formData.get('battery_level')) || 0,
|
||||
signal_strength: parseInt(formData.get('signal_strength')) || 0
|
||||
}
|
||||
};
|
||||
await makeApiCall('POST', '/api/v1/gateway/heartbeat', body);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user