completed SMS gateway project
This commit is contained in:
101
app/views/admin/api_keys/index.html.erb
Normal file
101
app/views/admin/api_keys/index.html.erb
Normal 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>
|
||||
105
app/views/admin/api_keys/new.html.erb
Normal file
105
app/views/admin/api_keys/new.html.erb
Normal 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>
|
||||
139
app/views/admin/api_keys/show.html.erb
Normal file
139
app/views/admin/api_keys/show.html.erb
Normal 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>
|
||||
Reference in New Issue
Block a user