completed SMS gateway project

This commit is contained in:
Min Zeya Phyo
2025-10-22 17:22:17 +08:00
commit c883fa7128
190 changed files with 16294 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
<div class="space-y-6">
<div class="sm:flex sm:items-center sm:justify-between">
<div>
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">API Keys</h1>
<p class="mt-2 text-sm text-gray-600">Manage API keys for client access and authentication</p>
</div>
<%= link_to new_admin_api_key_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>
Create New API Key
<% end %>
</div>
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5">
<% if @api_keys.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">Key Prefix</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Permissions</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">Last Used</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-right text-xs font-medium uppercase tracking-wide text-gray-500">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<% @api_keys.each do |api_key| %>
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-6 py-4">
<div class="text-sm font-medium text-gray-900"><%= api_key.name %></div>
</td>
<td class="px-6 py-4">
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= api_key.key_prefix %>...</code>
</td>
<td class="px-6 py-4">
<div class="flex flex-wrap gap-1">
<% perms = api_key.permissions || {} %>
<% perms = {} unless perms.is_a?(Hash) %>
<% if perms.any? %>
<% perms.select { |_, v| v }.keys.each do |perm| %>
<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">
<%= perm.to_s.humanize %>
</span>
<% end %>
<% 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">None</span>
<% end %>
</div>
</td>
<td class="px-6 py-4">
<% if api_key.active_and_valid? %>
<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>
Active
</span>
<% elsif !api_key.active %>
<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">Revoked</span>
<% else %>
<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">Expired</span>
<% end %>
</td>
<td class="px-6 py-4 text-sm text-gray-500">
<% if api_key.last_used_at %>
<%= time_ago_in_words(api_key.last_used_at) %> ago
<% else %>
Never
<% end %>
</td>
<td class="px-6 py-4 text-sm text-gray-500">
<%= api_key.created_at.strftime("%Y-%m-%d") %>
</td>
<td class="px-6 py-4 text-right text-sm font-medium">
<% if api_key.active %>
<%= button_to admin_api_key_path(api_key), method: :delete, class: "inline-flex items-center gap-1 text-red-600 hover:text-red-900", data: { confirm: "Are you sure you want to revoke this API key?" } do %>
<i class="fas fa-ban"></i>
Revoke
<% end %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="px-6 py-14 text-center">
<i class="fas fa-key text-4xl text-gray-300"></i>
<h3 class="mt-4 text-sm font-semibold text-gray-900">No API keys</h3>
<p class="mt-2 text-sm text-gray-500">Get started by creating a new API key.</p>
<div class="mt-6">
<%= link_to new_admin_api_key_path, class: "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" do %>
<i class="fas fa-plus"></i>
Create API Key
<% end %>
</div>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,105 @@
<div class="space-y-6">
<!-- Page header with back link -->
<div class="flex items-center gap-4">
<%= link_to admin_api_keys_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 API Keys
<% end %>
</div>
<div class="border-b border-gray-200 pb-5">
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Create New API Key</h1>
<p class="mt-2 text-sm text-gray-600">Generate a new API key with specific permissions for your application.</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_api_keys_path, method: :post, local: true, class: "space-y-6" do |f| %>
<!-- Name field -->
<div>
<%= label_tag "api_key[name]", "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 "api_key[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: "My Application",
required: true %>
</div>
<p class="mt-1 text-sm text-gray-500">A descriptive name to identify this API key</p>
</div>
<!-- Permissions checkboxes -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-3">Permissions</label>
<div class="space-y-3">
<label class="relative flex items-start">
<div class="flex items-center h-6">
<%= check_box_tag "api_key[send_sms]", "1", true,
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600" %>
</div>
<div class="ml-3">
<span class="text-sm font-medium text-gray-900">Send SMS</span>
<p class="text-sm text-gray-500">Allow sending outbound SMS messages</p>
</div>
</label>
<label class="relative flex items-start">
<div class="flex items-center h-6">
<%= check_box_tag "api_key[receive_sms]", "1", true,
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600" %>
</div>
<div class="ml-3">
<span class="text-sm font-medium text-gray-900">Receive SMS</span>
<p class="text-sm text-gray-500">Allow receiving and querying inbound SMS</p>
</div>
</label>
<label class="relative flex items-start">
<div class="flex items-center h-6">
<%= check_box_tag "api_key[manage_gateways]", "1", false,
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600" %>
</div>
<div class="ml-3">
<span class="text-sm font-medium text-gray-900">Manage Gateways</span>
<p class="text-sm text-gray-500">Allow managing gateway devices and settings</p>
</div>
</label>
<label class="relative flex items-start">
<div class="flex items-center h-6">
<%= check_box_tag "api_key[manage_otp]", "1", true,
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600" %>
</div>
<div class="ml-3">
<span class="text-sm font-medium text-gray-900">Manage OTP</span>
<p class="text-sm text-gray-500">Allow generating and verifying OTP codes</p>
</div>
</label>
</div>
</div>
<!-- Expiration field -->
<div>
<%= label_tag "api_key[expires_at]", "Expiration Date (Optional)", 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-calendar text-gray-400"></i>
</div>
<%= datetime_local_field_tag "api_key[expires_at]", 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" %>
</div>
<p class="mt-1 text-sm text-gray-500">Leave empty for no expiration</p>
</div>
<!-- Action buttons -->
<div class="flex items-center gap-3 pt-4">
<%= submit_tag "Create API Key",
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_api_keys_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>

View File

@@ -0,0 +1,139 @@
<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 Key Created Successfully!</h1>
<p class="mt-2 text-sm text-gray-600">Your new API key has been generated and is ready to use.</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 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. Make sure to copy it and store it securely.
If you lose it, you'll need to generate a new key.
</p>
</div>
</div>
</div>
<!-- API Key display 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">Your New API Key</h3>
<button
onclick="copyToClipboard('<%= @raw_key %>')"
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-copy"></i>
Copy to Clipboard
</button>
</div>
<div class="relative rounded-lg bg-gray-900 px-4 py-4 overflow-x-auto">
<code class="text-sm font-mono text-green-400 break-all"><%= @raw_key %></code>
</div>
</div>
<!-- API Key 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">API Key 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">Name</dt>
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0"><%= @api_key.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">Key Prefix</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"><%= @api_key.key_prefix %>...</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">Permissions</dt>
<dd class="mt-1 text-sm sm:col-span-2 sm:mt-0">
<div class="flex flex-wrap gap-2">
<% perms = @api_key.permissions || {} %>
<% perms = {} unless perms.is_a?(Hash) %>
<% perms.select { |_, v| v }.keys.each do |perm| %>
<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 px-3 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
<i class="fas fa-check"></i>
<%= perm.to_s.humanize %>
</span>
<% end %>
</div>
</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">Expiration</dt>
<dd class="mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0">
<% if @api_key.expires_at %>
<div class="flex items-center gap-2">
<i class="fas fa-calendar text-gray-400"></i>
<%= @api_key.expires_at.strftime("%B %d, %Y at %l:%M %p") %>
</div>
<% 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-infinity"></i>
Never expires
</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">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-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>
Active
</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">
<%= @api_key.created_at.strftime("%B %d, %Y at %l:%M %p") %>
</dd>
</div>
</dl>
</div>
<!-- Back link -->
<div class="flex items-center gap-3">
<%= link_to admin_api_keys_path,
class: "inline-flex 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" do %>
<i class="fas fa-arrow-left"></i>
Back to API Keys
<% end %>
</div>
</div>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
// Show success feedback
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-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);
});
}
</script>