completed SMS gateway project
This commit is contained in:
168
app/views/admin/gateways/index.html.erb
Normal file
168
app/views/admin/gateways/index.html.erb
Normal file
@@ -0,0 +1,168 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Page header -->
|
||||
<div class="sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Gateways</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Manage your SMS gateway devices and monitor their status.</p>
|
||||
</div>
|
||||
<%= link_to new_admin_gateway_path, class: "mt-4 sm:mt-0 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-all" do %>
|
||||
<i class="fas fa-plus"></i>
|
||||
Register New Gateway
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Gateways table card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 overflow-hidden">
|
||||
<% if @gateways.any? %>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Name</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Device ID</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Status</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Active</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Priority</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Today</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Total</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Last Heartbeat</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Created</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wide text-gray-500">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% @gateways.each do |gateway| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<!-- Name -->
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<%= link_to gateway.name, admin_gateway_path(gateway),
|
||||
class: "text-sm font-semibold text-blue-600 hover:text-blue-500 transition-colors" %>
|
||||
</td>
|
||||
|
||||
<!-- Device ID -->
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= gateway.device_id %></code>
|
||||
</td>
|
||||
|
||||
<!-- Status with pulse indicator -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<% if gateway.status == "online" %>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
||||
</span>
|
||||
Online
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">
|
||||
<span class="h-2 w-2 rounded-full bg-red-500"></span>
|
||||
Offline
|
||||
</span>
|
||||
<% end %>
|
||||
</td>
|
||||
|
||||
<!-- Active badge -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<% if gateway.active %>
|
||||
<span class="inline-flex items-center rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<i class="fas fa-check mr-1"></i> Active
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10">
|
||||
<i class="fas fa-times mr-1"></i> Inactive
|
||||
</span>
|
||||
<% end %>
|
||||
</td>
|
||||
|
||||
<!-- Priority -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900 font-medium text-center">
|
||||
<%= gateway.priority %>
|
||||
</td>
|
||||
|
||||
<!-- Messages Today -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 text-green-600">
|
||||
<i class="fas fa-arrow-up text-xs"></i>
|
||||
<span class="font-medium"><%= gateway.messages_sent_today %></span>
|
||||
</span>
|
||||
<span class="text-gray-300">|</span>
|
||||
<span class="inline-flex items-center gap-1 text-blue-600">
|
||||
<i class="fas fa-arrow-down text-xs"></i>
|
||||
<span class="font-medium"><%= gateway.messages_received_today %></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Total Messages -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 text-green-600">
|
||||
<i class="fas fa-arrow-up text-xs"></i>
|
||||
<span><%= gateway.total_messages_sent %></span>
|
||||
</span>
|
||||
<span class="text-gray-300">|</span>
|
||||
<span class="inline-flex items-center gap-1 text-blue-600">
|
||||
<i class="fas fa-arrow-down text-xs"></i>
|
||||
<span><%= gateway.total_messages_received %></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Last Heartbeat -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<% if gateway.last_heartbeat_at %>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-gray-900"><%= time_ago_in_words(gateway.last_heartbeat_at) %> ago</span>
|
||||
<span class="text-xs text-gray-400"><%= gateway.last_heartbeat_at.strftime("%m/%d/%y %H:%M") %></span>
|
||||
</div>
|
||||
<% else %>
|
||||
<span class="text-gray-400 italic">Never</span>
|
||||
<% end %>
|
||||
</td>
|
||||
|
||||
<!-- Created -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<%= gateway.created_at.strftime("%m/%d/%y") %>
|
||||
</td>
|
||||
|
||||
<!-- Actions -->
|
||||
<td class="whitespace-nowrap px-6 py-4 text-center">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<%= link_to test_admin_gateway_path(gateway),
|
||||
class: "inline-flex items-center gap-1 rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-blue-500 transition-all duration-200" do %>
|
||||
<i class="fas fa-vial"></i>
|
||||
Test
|
||||
<% end %>
|
||||
<%= button_to toggle_admin_gateway_path(gateway), method: :post,
|
||||
class: gateway.active ?
|
||||
"inline-flex items-center gap-2 rounded-lg bg-red-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-red-500 transition-all duration-200" :
|
||||
"inline-flex items-center gap-2 rounded-lg bg-green-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-green-500 transition-all duration-200" do %>
|
||||
<% if gateway.active %>
|
||||
<i class="fas fa-ban"></i>
|
||||
Deactivate
|
||||
<% else %>
|
||||
<i class="fas fa-check"></i>
|
||||
Activate
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<!-- Empty state -->
|
||||
<div class="px-6 py-14 text-center">
|
||||
<div class="mx-auto h-20 w-20 flex items-center justify-center rounded-full bg-gray-100">
|
||||
<i class="fas fa-mobile-alt text-3xl text-gray-400"></i>
|
||||
</div>
|
||||
<p class="mt-4 text-base font-medium text-gray-900">No gateways registered yet</p>
|
||||
<p class="mt-2 text-sm text-gray-500">Gateway devices will appear here once they connect via the API.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
87
app/views/admin/gateways/new.html.erb
Normal file
87
app/views/admin/gateways/new.html.erb
Normal file
@@ -0,0 +1,87 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Page header with back link -->
|
||||
<div class="flex items-center gap-4">
|
||||
<%= link_to admin_gateways_path, class: "inline-flex items-center gap-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors" do %>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Back to Gateways
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-gray-200 pb-5">
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Register New Gateway</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Add a new Android gateway device to your SMS system.</p>
|
||||
</div>
|
||||
|
||||
<!-- Form card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-8 max-w-2xl">
|
||||
<%= form_with url: admin_gateways_path, method: :post, local: true, class: "space-y-6" do |f| %>
|
||||
<!-- Device ID field -->
|
||||
<div>
|
||||
<%= label_tag "gateway[device_id]", "Device ID", class: "block text-sm font-medium text-gray-700" %>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-mobile-alt text-gray-400"></i>
|
||||
</div>
|
||||
<%= text_field_tag "gateway[device_id]", nil,
|
||||
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3",
|
||||
placeholder: "device-001",
|
||||
required: true %>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">A unique identifier for this gateway device (e.g., phone model, serial number)</p>
|
||||
</div>
|
||||
|
||||
<!-- Name field -->
|
||||
<div>
|
||||
<%= label_tag "gateway[name]", "Gateway Name", class: "block text-sm font-medium text-gray-700" %>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-tag text-gray-400"></i>
|
||||
</div>
|
||||
<%= text_field_tag "gateway[name]", nil,
|
||||
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3",
|
||||
placeholder: "Office Phone",
|
||||
required: true %>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">A friendly name to identify this gateway</p>
|
||||
</div>
|
||||
|
||||
<!-- Priority field -->
|
||||
<div>
|
||||
<%= label_tag "gateway[priority]", "Priority", class: "block text-sm font-medium text-gray-700" %>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-sort-numeric-up text-gray-400"></i>
|
||||
</div>
|
||||
<%= number_field_tag "gateway[priority]", 1,
|
||||
min: 1,
|
||||
max: 10,
|
||||
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3" %>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">Priority level (1-10). Higher priority gateways are used first for sending messages.</p>
|
||||
</div>
|
||||
|
||||
<!-- Information box -->
|
||||
<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">Gateway API Key</h3>
|
||||
<p class="mt-1 text-sm text-blue-700">
|
||||
After creating the gateway, you'll receive a unique API key. You'll need to configure this key in your Android gateway app to connect it to the system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<%= submit_tag "Register Gateway",
|
||||
class: "inline-flex justify-center items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-all duration-200" %>
|
||||
<%= link_to "Cancel", admin_gateways_path,
|
||||
class: "inline-flex justify-center items-center gap-2 rounded-lg bg-gray-100 px-6 py-3 text-sm font-semibold text-gray-700 hover:bg-gray-200 transition-all duration-200" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
565
app/views/admin/gateways/show.html.erb
Normal file
565
app/views/admin/gateways/show.html.erb
Normal file
@@ -0,0 +1,565 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Back link -->
|
||||
<div class="flex items-center gap-4">
|
||||
<%= link_to admin_gateways_path, class: "inline-flex items-center gap-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors" do %>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Back to Gateways
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @is_new && @raw_key.present? %>
|
||||
<!-- New gateway created - show API key -->
|
||||
<div class="border-b border-gray-200 pb-5">
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Gateway Created Successfully!</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Your new gateway has been registered and is ready to connect.</p>
|
||||
</div>
|
||||
|
||||
<!-- Warning alert -->
|
||||
<div class="rounded-lg bg-yellow-50 px-4 py-4 ring-1 ring-yellow-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-yellow-800">Important: Save this API key now!</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700">
|
||||
This is the only time you'll be able to see the full API key. You need to configure this key in your Android gateway app to connect it to the system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code and API Key display -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- QR Code Card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Scan QR Code</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">Scan this QR code with your Android gateway app to auto-configure</p>
|
||||
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="bg-white p-4 rounded-lg shadow-inner border-2 border-gray-200">
|
||||
<%= @qr_code_data.html_safe %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-blue-50 px-4 py-3 ring-1 ring-blue-600/10">
|
||||
<p class="text-xs text-blue-700">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
QR code contains API key, API base URL, and WebSocket URL
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Key Manual Entry Card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Manual Configuration</h3>
|
||||
<button
|
||||
onclick="copyAllConfig()"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-gray-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-gray-500 transition-all duration-200">
|
||||
<i class="fas fa-copy"></i>
|
||||
Copy All
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">Or manually enter these details if QR scanning is unavailable</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- API Base URL -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">API Base URL</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 relative rounded-lg bg-gray-50 px-3 py-2 border border-gray-200">
|
||||
<code class="text-xs font-mono text-gray-800 break-all" id="api-base-url"><%= request.base_url %></code>
|
||||
</div>
|
||||
<button
|
||||
onclick="copyField('api-base-url')"
|
||||
class="flex-shrink-0 inline-flex items-center gap-1 rounded-lg bg-blue-600 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-500">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WebSocket URL -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">WebSocket URL</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 relative rounded-lg bg-gray-50 px-3 py-2 border border-gray-200">
|
||||
<code class="text-xs font-mono text-gray-800 break-all" id="ws-url"><%= request.base_url.sub(/^http/, 'ws') %>/cable</code>
|
||||
</div>
|
||||
<button
|
||||
onclick="copyField('ws-url')"
|
||||
class="flex-shrink-0 inline-flex items-center gap-1 rounded-lg bg-blue-600 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-500">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Key -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">API Key</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 relative rounded-lg bg-gray-900 px-3 py-2">
|
||||
<code class="text-xs font-mono text-green-400 break-all" id="api-key"><%= @raw_key %></code>
|
||||
</div>
|
||||
<button
|
||||
onclick="copyField('api-key')"
|
||||
class="flex-shrink-0 inline-flex items-center gap-1 rounded-lg bg-blue-600 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-500">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gateway Details 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">Gateway Details</h3>
|
||||
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Device ID</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= @gateway.device_id %></code>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Name</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0"><%= @gateway.name %></dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Priority</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center rounded-full bg-purple-50 px-3 py-1 text-sm font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">
|
||||
<%= @gateway.priority %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Status</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-red-500"></span>
|
||||
Offline (Waiting for connection)
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Created</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<%= @gateway.created_at.strftime("%B %d, %Y at %l:%M %p") %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Next steps card -->
|
||||
<div class="rounded-lg bg-blue-50 px-6 py-6 ring-1 ring-blue-600/10">
|
||||
<h3 class="text-sm font-semibold text-blue-800 mb-3">Quick Setup Guide</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<h4 class="text-xs font-semibold text-blue-800 mb-2">Option 1: QR Code (Recommended)</h4>
|
||||
<ol class="list-decimal list-inside space-y-1.5 text-xs text-blue-700 ml-2">
|
||||
<li>Install the Android SMS Gateway app on your device</li>
|
||||
<li>Open the app and look for "Scan QR Code" option</li>
|
||||
<li>Scan the QR code above - configuration will be applied automatically</li>
|
||||
<li>Start the gateway service in the app</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-xs font-semibold text-blue-800 mb-2">Option 2: Manual Entry</h4>
|
||||
<ol class="list-decimal list-inside space-y-1.5 text-xs text-blue-700 ml-2">
|
||||
<li>Install the Android SMS Gateway app on your device</li>
|
||||
<li>Open the app and navigate to Settings</li>
|
||||
<li>Copy and paste each field from the "Manual Configuration" section above</li>
|
||||
<li>Save the configuration and start the gateway service</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-blue-200">
|
||||
<p class="text-xs text-blue-700">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
The gateway will appear as <span class="font-semibold">"Online"</span> once it successfully connects to the server.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyField(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
const text = element.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
const button = event.target.closest('button');
|
||||
const originalHTML = button.innerHTML;
|
||||
button.innerHTML = '<i class="fas fa-check"></i>';
|
||||
button.classList.add('bg-green-600', 'hover:bg-green-500');
|
||||
button.classList.remove('bg-blue-600', 'hover:bg-blue-500');
|
||||
|
||||
setTimeout(function() {
|
||||
button.innerHTML = originalHTML;
|
||||
button.classList.remove('bg-green-600', 'hover:bg-green-500');
|
||||
button.classList.add('bg-blue-600', 'hover:bg-blue-500');
|
||||
}, 2000);
|
||||
}, function(err) {
|
||||
alert('Failed to copy: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function copyAllConfig() {
|
||||
const apiBaseUrl = document.getElementById('api-base-url').textContent;
|
||||
const wsUrl = document.getElementById('ws-url').textContent;
|
||||
const apiKey = document.getElementById('api-key').textContent;
|
||||
|
||||
const configText = `API Base URL: ${apiBaseUrl}\nWebSocket URL: ${wsUrl}\nAPI Key: ${apiKey}`;
|
||||
|
||||
navigator.clipboard.writeText(configText).then(function() {
|
||||
const button = event.target.closest('button');
|
||||
const originalHTML = button.innerHTML;
|
||||
button.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
||||
button.classList.add('bg-green-600', 'hover:bg-green-500');
|
||||
button.classList.remove('bg-gray-600', 'hover:bg-gray-500');
|
||||
|
||||
setTimeout(function() {
|
||||
button.innerHTML = originalHTML;
|
||||
button.classList.remove('bg-green-600', 'hover:bg-green-500');
|
||||
button.classList.add('bg-gray-600', 'hover:bg-gray-500');
|
||||
}, 2000);
|
||||
}, function(err) {
|
||||
alert('Failed to copy: ' + err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<% else %>
|
||||
<!-- Existing gateway view -->
|
||||
<!-- Page header -->
|
||||
<div class="border-b border-gray-200 pb-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900"><%= @gateway.name %></h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Gateway device details and statistics</p>
|
||||
</div>
|
||||
<!-- Status indicator -->
|
||||
<div>
|
||||
<% if @gateway.status == "online" %>
|
||||
<span class="inline-flex items-center gap-2 rounded-full bg-green-50 px-4 py-2 text-sm font-medium text-green-700 ring-2 ring-inset ring-green-700/20">
|
||||
<span class="relative flex h-3 w-3">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
|
||||
</span>
|
||||
Online
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-2 rounded-full bg-red-50 px-4 py-2 text-sm font-medium text-red-700 ring-2 ring-inset ring-red-700/20">
|
||||
<span class="h-3 w-3 rounded-full bg-red-500"></span>
|
||||
Offline
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats grid -->
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Status card -->
|
||||
<div class="relative overflow-hidden rounded-xl <%= @gateway.status == 'online' ? 'bg-green-500' : 'bg-red-500' %> px-4 py-5 shadow-sm sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-white/20 p-3">
|
||||
<i class="fas fa-signal text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-white/90">Connection Status</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-white"><%= @gateway.status.titleize %></p>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<!-- Active status card -->
|
||||
<div class="relative overflow-hidden rounded-xl <%= @gateway.active ? 'bg-blue-500' : 'bg-gray-400' %> px-4 py-5 shadow-sm sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-white/20 p-3">
|
||||
<i class="fas fa-power-off text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-white/90">Active Status</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-white"><%= @gateway.active ? 'Active' : 'Inactive' %></p>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<!-- Priority card -->
|
||||
<div class="relative overflow-hidden rounded-xl bg-purple-500 px-4 py-5 shadow-sm sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-white/20 p-3">
|
||||
<i class="fas fa-sort-amount-up text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-white/90">Priority Level</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-white"><%= @gateway.priority %></p>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<!-- Messages sent today -->
|
||||
<div class="relative overflow-hidden rounded-xl bg-white px-4 py-5 shadow-sm ring-1 ring-gray-900/5 sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-green-500 p-3">
|
||||
<i class="fas fa-arrow-up text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-gray-500">Messages Sent Today</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-gray-900"><%= @gateway.messages_sent_today %></p>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<!-- Messages received today -->
|
||||
<div class="relative overflow-hidden rounded-xl bg-white px-4 py-5 shadow-sm ring-1 ring-gray-900/5 sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-blue-500 p-3">
|
||||
<i class="fas fa-arrow-down text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-gray-500">Messages Received Today</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-gray-900"><%= @gateway.messages_received_today %></p>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<!-- Total messages -->
|
||||
<div class="relative overflow-hidden rounded-xl bg-white px-4 py-5 shadow-sm ring-1 ring-gray-900/5 sm:px-6">
|
||||
<dt>
|
||||
<div class="absolute rounded-lg bg-yellow-500 p-3">
|
||||
<i class="fas fa-paper-plane text-xl text-white"></i>
|
||||
</div>
|
||||
<p class="ml-16 truncate text-sm font-medium text-gray-500">Total Messages</p>
|
||||
</dt>
|
||||
<dd class="ml-16 flex items-baseline">
|
||||
<p class="text-2xl font-semibold text-gray-900"><%= @gateway.total_messages_sent + @gateway.total_messages_received %></p>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Details 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">Gateway Details</h3>
|
||||
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Device ID</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<code class="rounded bg-gray-100 px-3 py-1.5 text-sm font-mono text-gray-800"><%= @gateway.device_id %></code>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Name</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0"><%= @gateway.name %></dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Status</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<% if @gateway.status == "online" %>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-green-500"></span>
|
||||
Online
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-red-500"></span>
|
||||
Offline
|
||||
</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Active</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<% if @gateway.active %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<i class="fas fa-check"></i> Active
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10">
|
||||
<i class="fas fa-times"></i> Inactive
|
||||
</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Priority</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center rounded-full bg-purple-50 px-3 py-1 text-sm font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">
|
||||
<%= @gateway.priority %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Last Heartbeat</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<% if @gateway.last_heartbeat_at %>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fas fa-heartbeat text-red-500"></i>
|
||||
<span><%= @gateway.last_heartbeat_at.strftime("%B %d, %Y at %l:%M:%S %p") %></span>
|
||||
<span class="text-gray-500">(<%= time_ago_in_words(@gateway.last_heartbeat_at) %> ago)</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<span class="text-gray-400 italic">Never</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Total Messages Sent</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center gap-1 text-green-600 font-semibold">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
<%= @gateway.total_messages_sent %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Total Messages Received</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center gap-1 text-blue-600 font-semibold">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
<%= @gateway.total_messages_received %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Created</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<%= @gateway.created_at.strftime("%B %d, %Y at %l:%M %p") %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Last Updated</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
|
||||
<%= @gateway.updated_at.strftime("%B %d, %Y at %l:%M %p") %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<!-- Metadata section -->
|
||||
<% if @gateway.metadata.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-sm font-semibold text-gray-900 mb-3">Device Metadata</h4>
|
||||
<div class="rounded-lg bg-gray-900 px-4 py-4 overflow-x-auto">
|
||||
<pre class="text-xs font-mono text-green-400"><%= JSON.pretty_generate(@gateway.metadata) %></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<div class="flex items-center gap-3">
|
||||
<%= link_to test_admin_gateway_path(@gateway),
|
||||
class: "inline-flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 transition-all duration-200" do %>
|
||||
<i class="fas fa-vial"></i>
|
||||
Test Gateway
|
||||
<% end %>
|
||||
<%= button_to toggle_admin_gateway_path(@gateway), method: :post,
|
||||
class: @gateway.active ?
|
||||
"inline-flex items-center gap-2 rounded-lg bg-red-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-red-500 transition-all duration-200" :
|
||||
"inline-flex items-center gap-2 rounded-lg bg-green-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-green-500 transition-all duration-200" do %>
|
||||
<% if @gateway.active %>
|
||||
<i class="fas fa-ban"></i>
|
||||
Deactivate Gateway
|
||||
<% else %>
|
||||
<i class="fas fa-check"></i>
|
||||
Activate Gateway
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent messages card -->
|
||||
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Recent Messages</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">Last <%= @recent_messages.size %> messages from this gateway</p>
|
||||
</div>
|
||||
|
||||
<% if @recent_messages.any? %>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Message ID</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Phone Number</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Direction</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Status</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% @recent_messages.each do |msg| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= msg.message_id[0..15] %>...</code>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
|
||||
<%= msg.phone_number %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<% if msg.direction == "outbound" %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
|
||||
<i class="fas fa-arrow-up"></i> Outbound
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<i class="fas fa-arrow-down"></i> Inbound
|
||||
</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<% case msg.status %>
|
||||
<% when "delivered" %>
|
||||
<span class="inline-flex rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">Delivered</span>
|
||||
<% when "sent" %>
|
||||
<span class="inline-flex rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">Sent</span>
|
||||
<% when "failed" %>
|
||||
<span class="inline-flex rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">Failed</span>
|
||||
<% when "pending" %>
|
||||
<span class="inline-flex rounded-full bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-yellow-700/10">Pending</span>
|
||||
<% else %>
|
||||
<span class="inline-flex rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10"><%= msg.status.titleize %></span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<%= msg.created_at.strftime("%m/%d/%y %H:%M") %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="px-6 py-14 text-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300"></i>
|
||||
<p class="mt-4 text-sm font-medium text-gray-900">No messages yet</p>
|
||||
<p class="mt-2 text-sm text-gray-500">Messages will appear here once this gateway starts processing SMS.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
353
app/views/admin/gateways/test.html.erb
Normal file
353
app/views/admin/gateways/test.html.erb
Normal file
@@ -0,0 +1,353 @@
|
||||
<% if @gateway.nil? %>
|
||||
<div class="rounded-lg bg-red-50 px-4 py-4 ring-1 ring-red-600/10">
|
||||
<p class="text-red-800">Error: Gateway not found</p>
|
||||
<%= link_to "Back to Gateways", admin_gateways_path, class: "text-red-600 underline" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="space-y-6">
|
||||
<!-- Back link -->
|
||||
<div class="flex items-center gap-4">
|
||||
<%= link_to admin_gateway_path(@gateway), class: "inline-flex items-center gap-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors" do %>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Back to Gateway Details
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="border-b border-gray-200 pb-5">
|
||||
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Test Gateway: <%= @gateway.name %></h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Test connection and send test SMS messages to verify gateway functionality.</p>
|
||||
</div>
|
||||
|
||||
<!-- Gateway Status Card -->
|
||||
<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">Gateway Status</h3>
|
||||
<button
|
||||
onclick="checkConnection()"
|
||||
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 transition-all duration-200">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="status-container" class="space-y-4">
|
||||
<!-- Status will be loaded here -->
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connection Information 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">Connection Information</h3>
|
||||
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Device ID</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= @gateway.device_id %></code>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Gateway Name</dt>
|
||||
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0"><%= @gateway.name %></dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Priority</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="inline-flex items-center rounded-full bg-purple-50 px-3 py-1 text-sm font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">
|
||||
<%= @gateway.priority %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-gray-900">Active</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
|
||||
<% if @gateway.active %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
||||
<i class="fas fa-check"></i> Active
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">
|
||||
<i class="fas fa-times"></i> Inactive
|
||||
</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Send Test SMS 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">Send Test SMS</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">Send a test SMS message through this gateway to verify it's working correctly.</p>
|
||||
|
||||
<form id="test-sms-form" class="space-y-6">
|
||||
<!-- Phone Number -->
|
||||
<div>
|
||||
<label for="phone_number" class="block text-sm font-medium text-gray-700">Phone Number</label>
|
||||
<div class="mt-1 relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<i class="fas fa-phone text-gray-400"></i>
|
||||
</div>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone_number"
|
||||
name="phone_number"
|
||||
class="block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3"
|
||||
placeholder="+959123456789"
|
||||
required>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">Enter phone number with country code (e.g., +959123456789)</p>
|
||||
</div>
|
||||
|
||||
<!-- Message Body -->
|
||||
<div>
|
||||
<label for="message_body" class="block text-sm font-medium text-gray-700">Message</label>
|
||||
<div class="mt-1">
|
||||
<textarea
|
||||
id="message_body"
|
||||
name="message_body"
|
||||
rows="4"
|
||||
class="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm py-3"
|
||||
placeholder="This is a test message from the admin interface."
|
||||
required>This is a test message from MySMSAPio admin interface. Gateway: <%= @gateway.name %></textarea>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500" id="char-count">160 characters remaining</p>
|
||||
</div>
|
||||
|
||||
<!-- Result Display -->
|
||||
<div id="sms-result" class="hidden">
|
||||
<!-- Success/Error message will be displayed here -->
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex justify-center items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-all duration-200">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
<span id="submit-text">Send Test SMS</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="resetForm()"
|
||||
class="inline-flex justify-center items-center gap-2 rounded-lg bg-gray-100 px-6 py-3 text-sm font-semibold text-gray-700 hover:bg-gray-200 transition-all duration-200">
|
||||
<i class="fas fa-redo"></i>
|
||||
Reset Form
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Warning Card -->
|
||||
<div class="rounded-lg bg-yellow-50 px-4 py-4 ring-1 ring-yellow-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-yellow-800">Important Notes</h3>
|
||||
<ul class="mt-2 text-sm text-yellow-700 list-disc list-inside space-y-1">
|
||||
<li>Test SMS messages will be sent to real phone numbers</li>
|
||||
<li>Ensure the gateway is online and connected before testing</li>
|
||||
<li>Standard SMS charges may apply to the recipient</li>
|
||||
<li>Test messages are marked with metadata for identification</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Check connection on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkConnection();
|
||||
updateCharCount();
|
||||
});
|
||||
|
||||
// Update character count
|
||||
const messageBody = document.getElementById('message_body');
|
||||
messageBody.addEventListener('input', updateCharCount);
|
||||
|
||||
function updateCharCount() {
|
||||
const length = messageBody.value.length;
|
||||
const remaining = 160 - length;
|
||||
const charCount = document.getElementById('char-count');
|
||||
|
||||
if (remaining < 0) {
|
||||
charCount.textContent = `${Math.abs(remaining)} characters over limit (message will be split into ${Math.ceil(length / 160)} parts)`;
|
||||
charCount.classList.add('text-red-600');
|
||||
charCount.classList.remove('text-gray-500');
|
||||
} else {
|
||||
charCount.textContent = `${remaining} characters remaining`;
|
||||
charCount.classList.remove('text-red-600');
|
||||
charCount.classList.add('text-gray-500');
|
||||
}
|
||||
}
|
||||
|
||||
// Check gateway connection
|
||||
async function checkConnection() {
|
||||
const container = document.getElementById('status-container');
|
||||
container.innerHTML = '<div class="flex items-center justify-center py-8"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('<%= check_connection_admin_gateway_path(@gateway) %>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
container.innerHTML = `
|
||||
<div class="rounded-lg bg-green-50 px-4 py-4 ring-1 ring-green-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-check-circle text-green-600 text-2xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-green-800">Gateway is Online</h3>
|
||||
<p class="mt-1 text-sm text-green-700">
|
||||
Last heartbeat: ${data.time_ago} ago
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-green-600">
|
||||
${data.last_heartbeat}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="rounded-lg bg-red-50 px-4 py-4 ring-1 ring-red-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-times-circle text-red-600 text-2xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-red-800">Gateway is Offline</h3>
|
||||
<p class="mt-1 text-sm text-red-700">
|
||||
${data.message}
|
||||
</p>
|
||||
${data.last_heartbeat ? `<p class="mt-1 text-xs text-red-600">Last seen: ${data.time_ago} ago</p>` : '<p class="mt-1 text-xs text-red-600">Never connected</p>'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="rounded-lg bg-red-50 px-4 py-4 ring-1 ring-red-600/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-circle text-red-600 text-2xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-red-800">Error Checking Status</h3>
|
||||
<p class="mt-1 text-sm text-red-700">${error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
document.getElementById('test-sms-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitButton = e.target.querySelector('button[type="submit"]');
|
||||
const submitText = document.getElementById('submit-text');
|
||||
const resultDiv = document.getElementById('sms-result');
|
||||
|
||||
// Disable button and show loading
|
||||
submitButton.disabled = true;
|
||||
submitText.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
||||
|
||||
const formData = {
|
||||
phone_number: document.getElementById('phone_number').value,
|
||||
message_body: document.getElementById('message_body').value
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('<%= send_test_sms_admin_gateway_path(@gateway) %>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
resultDiv.className = 'rounded-lg bg-green-50 px-4 py-4 ring-1 ring-green-600/10';
|
||||
resultDiv.innerHTML = `
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-check-circle text-green-600 text-xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-green-800">Test SMS Sent Successfully!</h3>
|
||||
<p class="mt-1 text-sm text-green-700">${data.message}</p>
|
||||
<div class="mt-2 text-xs text-green-600">
|
||||
<p>Message ID: <code class="bg-green-100 px-2 py-1 rounded">${data.message_id}</code></p>
|
||||
<p class="mt-1">Status: <span class="font-semibold">${data.sms_status}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
resultDiv.className = 'rounded-lg bg-red-50 px-4 py-4 ring-1 ring-red-600/10';
|
||||
resultDiv.innerHTML = `
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-times-circle text-red-600 text-xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-red-800">Failed to Send Test SMS</h3>
|
||||
<p class="mt-1 text-sm text-red-700">${data.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
resultDiv.classList.remove('hidden');
|
||||
} catch (error) {
|
||||
resultDiv.className = 'rounded-lg bg-red-50 px-4 py-4 ring-1 ring-red-600/10';
|
||||
resultDiv.innerHTML = `
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-circle text-red-600 text-xl"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sm font-semibold text-red-800">Error</h3>
|
||||
<p class="mt-1 text-sm text-red-700">${error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
resultDiv.classList.remove('hidden');
|
||||
} finally {
|
||||
// Re-enable button
|
||||
submitButton.disabled = false;
|
||||
submitText.innerHTML = '<i class="fas fa-paper-plane"></i> Send Test SMS';
|
||||
}
|
||||
});
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('test-sms-form').reset();
|
||||
document.getElementById('sms-result').classList.add('hidden');
|
||||
updateCharCount();
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
Reference in New Issue
Block a user