Files
MySMSAPio/app/views/admin/api_tester/index.html.erb
2025-10-22 17:22:17 +08:00

467 lines
18 KiB
Plaintext

<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>