Migrate to Postgresql for better network handling. Add more user functionality.

This commit is contained in:
Dan Milne
2025-11-06 14:08:39 +11:00
parent 85252a1a07
commit fc567f0b91
69 changed files with 4266 additions and 952 deletions

View File

@@ -0,0 +1,213 @@
<% content_for :title, "Edit Rule ##{@rule.id}" %>
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-gray-900">Edit Rule #<%= @rule.id %></h1>
<p class="mt-2 text-gray-600">Modify the WAF rule configuration</p>
</div>
</div>
</div>
<div class="bg-white shadow rounded-lg">
<%= form_with(model: @rule, local: true, class: "space-y-6") do |form| %>
<% if @rule.errors.any? %>
<div class="rounded-md bg-red-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
There were <%= pluralize(@rule.errors.count, "error") %> with your submission:
</h3>
<div class="mt-2 text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
<% @rule.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
</div>
</div>
</div>
<% end %>
<!-- Rule Type Selection -->
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Rule Configuration</h3>
</div>
<div class="px-6 py-4 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<%= form.label :rule_type, "Rule Type", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :rule_type,
options_for_select(@rule_types.map { |type| [type.humanize, type] }, @rule.rule_type),
{ prompt: "Select rule type" },
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
id: "rule_type_select",
disabled: true } %>
<p class="mt-2 text-sm text-gray-500">Rule type cannot be changed after creation</p>
</div>
<div>
<%= form.label :action, "Action", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :action,
options_for_select(@actions.map { |action| [action.humanize, action] }, @rule.action),
{ },
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
<p class="mt-2 text-sm text-gray-500">What action to take when this rule matches</p>
</div>
</div>
<!-- Network Range Selection (shown for network rules) -->
<% if @rule.network_rule? %>
<div id="network_range_section">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<%= form.label :network_range_id, "Network Range", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :network_range_id,
options_from_collection_for_select(NetworkRange.order(:network).limit(100), :id, :cidr, @rule.network_range_id),
{ prompt: "Select a network range" },
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
<p class="mt-2 text-sm text-gray-500">Select from recent network ranges or create new ones</p>
</div>
<div class="flex items-end">
<%= link_to "Create New Network Range", new_network_range_path,
class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
</div>
</div>
<% if @rule.network_range.present? %>
<div class="bg-blue-50 border border-blue-200 rounded-md p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-blue-800">
Currently targeting: <strong><%= link_to @rule.network_range.cidr, network_range_path(@rule.network_range), class: "text-blue-600 hover:text-blue-900 underline" %></strong>
<% if @rule.network_range.company.present? %>
- <%= @rule.network_range.company %>
<% end %>
</p>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
<!-- Conditions (shown for non-network rules) -->
<% unless @rule.network_rule? %>
<div id="conditions_section">
<div>
<%= form.label :conditions, "Conditions", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_area :conditions, rows: 4,
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: '{"path_pattern": "/admin/*", "user_agent": "bot*"}' %>
<p class="mt-2 text-sm text-gray-500">JSON format with matching conditions</p>
</div>
</div>
<% end %>
<!-- Metadata -->
<div data-controller="json-validator" data-json-validator-valid-class="json-valid" data-json-validator-invalid-class="json-invalid" data-json-validator-valid-status-class="json-valid-status" data-json-validator-invalid-status-class="json-invalid-status">
<%= form.label :metadata, "Metadata", class: "block text-sm font-medium text-gray-700" %>
<div class="relative">
<%= form.text_area :metadata, rows: 3,
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: '{"reason": "Suspicious activity detected", "source": "manual"}',
data: { json_validator_target: "textarea", action: "input->json-validator#validate" } %>
<div class="mt-1 flex items-center justify-between">
<div data-json-validator-target="status" class="text-sm"></div>
<div class="flex space-x-2">
<button type="button"
data-action="click->json-validator#format"
class="text-xs text-gray-500 hover:text-gray-700 underline">
Format JSON
</button>
<button type="button"
data-action="click->json-validator#insertSample"
data-json-validator-json-sample='{"reason": "Block malicious ISP", "threat_type": "botnet", "confidence": "high", "source": "manual"}'
class="text-xs text-gray-500 hover:text-gray-700 underline">
Insert Sample
</button>
</div>
</div>
</div>
<p class="mt-2 text-sm text-gray-500">JSON format with additional metadata</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<%= form.label :expires_at, "Expires At", class: "block text-sm font-medium text-gray-700" %>
<%= form.datetime_local_field :expires_at,
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
<p class="mt-2 text-sm text-gray-500">Leave blank for permanent rule</p>
</div>
<div>
<%= form.label :source, "Source", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :source,
options_for_select(Rule::SOURCES.map { |source| [source.humanize, source] }, @rule.source),
{ },
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
<p class="mt-2 text-sm text-gray-500">How this rule was created</p>
</div>
<div class="flex items-center pt-6">
<%= form.check_box :enabled, class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
<%= form.label :enabled, "Rule Enabled", class: "ml-2 block text-sm text-gray-900" %>
</div>
</div>
</div>
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
<div class="flex justify-between">
<div class="flex space-x-3">
<%= link_to "Cancel", @rule, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
</div>
<div class="flex space-x-3">
<%= link_to "Delete Rule", @rule,
method: :delete,
data: { confirm: "Are you sure you want to delete this rule? This action cannot be undone." },
class: "inline-flex items-center px-4 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-red-50 hover:bg-red-100" %>
<%= form.submit "Update Rule", class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700" %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Show/hide sections based on rule type
const ruleTypeSelect = document.getElementById('rule_type_select');
const networkSection = document.getElementById('network_range_section');
const conditionsSection = document.getElementById('conditions_section');
function toggleSections() {
if (ruleTypeSelect && ruleTypeSelect.value === 'network') {
networkSection.classList.remove('hidden');
if (conditionsSection) conditionsSection.classList.add('hidden');
} else {
if (networkSection) networkSection.classList.add('hidden');
if (conditionsSection) conditionsSection.classList.remove('hidden');
}
}
if (ruleTypeSelect) {
ruleTypeSelect.addEventListener('change', toggleSections);
toggleSections(); // Initial state
}
});
</script>