273 lines
13 KiB
Plaintext
273 lines
13 KiB
Plaintext
<%# Helper methods %>
|
|
<% def status_badge_class(status) %>
|
|
<% case status %>
|
|
<% when 'pending' %>
|
|
bg-gray-100 text-gray-800
|
|
<% when 'processing' %>
|
|
bg-blue-100 text-blue-800
|
|
<% when 'completed' %>
|
|
bg-green-100 text-green-800
|
|
<% when 'failed' %>
|
|
bg-red-100 text-red-800
|
|
<% else %>
|
|
bg-gray-100 text-gray-800
|
|
<% end %>
|
|
<% end %>
|
|
|
|
<div class="max-w-7xl mx-auto">
|
|
<!-- Header -->
|
|
<div class="bg-white shadow-sm rounded-lg mb-6">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-semibold text-gray-900">GeoLite2 Data Imports</h1>
|
|
<p class="mt-1 text-sm text-gray-600">
|
|
Manage and monitor your GeoLite2 database imports.
|
|
</p>
|
|
</div>
|
|
<%= link_to "New Import", new_data_import_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="bg-white shadow-sm rounded-lg mb-6">
|
|
<div class="px-6 py-4">
|
|
<%= form_with(url: data_imports_path, method: :get, local: true) do |f| %>
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<%= f.label :import_type, "Import Type", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= f.select :import_type,
|
|
options_for_select([['All Types', ''], ['ASN', 'asn'], ['Country', 'country']], params[:import_type]),
|
|
{ }, { class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
|
</div>
|
|
<div>
|
|
<%= f.label :status, "Status", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= f.select :status,
|
|
options_for_select([['All Statuses', ''], ['Pending', 'pending'], ['Processing', 'processing'], ['Completed', 'completed'], ['Failed', 'failed']], params[:status]),
|
|
{ }, { class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
|
</div>
|
|
<div>
|
|
<%= f.label :filename, "Filename", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= f.text_field :filename, value: params[:filename], class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "Search filename..." %>
|
|
</div>
|
|
<div class="flex items-end">
|
|
<%= f.submit "Filter", class: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Overview -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
|
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-semibold text-gray-900"><%= DataImport.count %></div>
|
|
<div class="text-sm text-gray-600">Total Imports</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-semibold text-gray-900"><%= DataImport.completed.count %></div>
|
|
<div class="text-sm text-gray-600">Completed</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-semibold text-gray-900"><%= DataImport.processing.count %></div>
|
|
<div class="text-sm text-gray-600">Processing</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-red-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-semibold text-gray-900"><%= DataImport.failed.count %></div>
|
|
<div class="text-sm text-gray-600">Failed</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Imports Table -->
|
|
<div class="bg-white shadow-sm rounded-lg">
|
|
<div class="overflow-hidden">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Filename
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Type
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Progress
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Created
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Duration
|
|
</th>
|
|
<th class="relative px-6 py-3">
|
|
<span class="sr-only">Actions</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<% if @data_imports.any? %>
|
|
<% @data_imports.each do |data_import| %>
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
<%= link_to data_import, class: "flex items-center text-blue-600 hover:text-blue-900 hover:underline" do %>
|
|
<svg class="w-4 h-4 text-gray-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<%= truncate(data_import.filename, length: 40) %>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= data_import.import_type == 'asn' ? 'bg-purple-100 text-purple-800' : 'bg-indigo-100 text-indigo-800' %>">
|
|
<%= data_import.import_type.upcase %>
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= status_badge_class(data_import.status) %>">
|
|
<%= data_import.status.capitalize %>
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
<%= link_to data_import, class: "block hover:bg-gray-50 -mx-2 px-2 py-1 rounded" do %>
|
|
<% if data_import.processing? || data_import.total_records > 0 %>
|
|
<div class="flex items-center">
|
|
<div class="flex-1 mr-2">
|
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
|
<div class="bg-blue-600 h-2 rounded-full" style="width: <%= data_import.progress_percentage %>%"></div>
|
|
</div>
|
|
</div>
|
|
<span class="text-xs text-gray-600">
|
|
<% if data_import.processed_records > 0 %>
|
|
<% if data_import.total_records > 0 && data_import.processed_records >= data_import.total_records %>
|
|
<%= number_with_delimiter(data_import.processed_records) %> total
|
|
<% else %>
|
|
<%= number_with_delimiter(data_import.processed_records) %> imported
|
|
<% end %>
|
|
<% else %>
|
|
Initializing...
|
|
<% end %>
|
|
</span>
|
|
</div>
|
|
<% else %>
|
|
<span class="text-gray-400">Not started</span>
|
|
<% end %>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
<%= data_import.created_at.strftime('%Y-%m-%d %H:%M') %>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
<% if data_import.duration > 0 %>
|
|
<%= distance_of_time_in_words(data_import.duration) %>
|
|
<% else %>
|
|
-
|
|
<% end %>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<% unless data_import.processing? %>
|
|
<%= link_to "Delete", data_import, method: :delete,
|
|
data: {
|
|
confirm: "Are you sure you want to delete this import?"
|
|
},
|
|
class: "text-red-600 hover:text-red-900" %>
|
|
<% else %>
|
|
<span class="text-gray-400">Processing...</span>
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
<% else %>
|
|
<tr>
|
|
<td colspan="7" class="px-6 py-12 text-center">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No imports found</h3>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
<% if params[:import_type].present? || params[:status].present? || params[:filename].present? %>
|
|
Try adjusting your search filters or
|
|
<% else %>
|
|
Get started by uploading your first
|
|
<% end %>
|
|
<%= link_to "GeoLite2 import", new_data_import_path, class: "text-blue-600 hover:text-blue-500" %>.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<% if @pagy.pages > 1 %>
|
|
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-700">
|
|
Showing
|
|
<span class="font-medium"><%= @pagy.from %></span>
|
|
to
|
|
<span class="font-medium"><%= @pagy.to %></span>
|
|
of
|
|
<span class="font-medium"><%= @pagy.count %></span>
|
|
results
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<%= pagy_nav_tailwind(@pagy) %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div> |