diff --git a/app/views/rules/_compact_rule.html.erb b/app/views/rules/_compact_rule.html.erb
new file mode 100644
index 0000000..70235e7
--- /dev/null
+++ b/app/views/rules/_compact_rule.html.erb
@@ -0,0 +1,33 @@
+<%# Compact rule display for showing rules on network range pages %>
+
+
+ <%= link_to rule, class: "flex items-center space-x-2 min-w-0 hover:text-blue-600" do %>
+ <%# Action badge %>
+
+ <%= rule.waf_action.upcase %>
+
+
+ <%# Network CIDR %>
+ <%= rule.network_range.cidr %>
+
+ <%# Priority %>
+ P:<%= rule.priority %>
+ <% end %>
+
+
+
+ <%# Disabled badge %>
+ <% unless rule.enabled? %>
+
+ Disabled
+
+ <% end %>
+
+ <%# Policy badge if policy-generated %>
+ <% if rule.waf_policy.present? %>
+
+ Policy
+
+ <% end %>
+
+
diff --git a/db/migrate/20251113043408_remove_legacy_columns_from_rules.rb b/db/migrate/20251113043408_remove_legacy_columns_from_rules.rb
new file mode 100644
index 0000000..8ffe7a3
--- /dev/null
+++ b/db/migrate/20251113043408_remove_legacy_columns_from_rules.rb
@@ -0,0 +1,12 @@
+class RemoveLegacyColumnsFromRules < ActiveRecord::Migration[8.1]
+ def change
+ # Remove indexes first
+ remove_index :rules, name: "index_rules_on_action" if index_exists?(:rules, name: "index_rules_on_action")
+ remove_index :rules, name: "index_rules_on_rule_type" if index_exists?(:rules, name: "index_rules_on_rule_type")
+ remove_index :rules, name: "idx_rules_type_enabled" if index_exists?(:rules, name: "idx_rules_type_enabled")
+
+ # Remove the legacy columns
+ remove_column :rules, :action, :string
+ remove_column :rules, :rule_type, :string
+ end
+end
diff --git a/db/migrate/20251113052831_add_time_aware_unique_indexes_to_rules.rb b/db/migrate/20251113052831_add_time_aware_unique_indexes_to_rules.rb
new file mode 100644
index 0000000..c3da959
--- /dev/null
+++ b/db/migrate/20251113052831_add_time_aware_unique_indexes_to_rules.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+class AddTimeAwareUniqueIndexesToRules < ActiveRecord::Migration[8.1]
+ def up
+ # First, clean up existing duplicate records to allow unique constraints
+ cleanup_duplicate_rules
+
+ # Add time-aware unique indexes for policy-generated rules
+ # This prevents exact duplicate time windows while allowing temporal flexibility
+
+ # For temporary rules (with expiration dates)
+ add_index :rules,
+ [:network_range_id, :waf_action, :waf_policy_id, :expires_at],
+ name: 'index_rules_on_network_policy_expires_unique',
+ unique: true,
+ where: "source = 'policy' AND expires_at IS NOT NULL"
+
+ # For permanent rules (no expiration date)
+ add_index :rules,
+ [:network_range_id, :waf_action, :waf_policy_id],
+ name: 'index_rules_on_network_policy_unique',
+ unique: true,
+ where: "source = 'policy' AND expires_at IS NULL"
+
+ # Additional indexes for performance
+ add_index :rules, [:source, :expires_at], name: 'index_rules_on_source_expires'
+ add_index :rules, [:waf_policy_id, :expires_at], name: 'index_rules_on_policy_expires'
+ end
+
+ def down
+ remove_index :rules, name: 'index_rules_on_network_policy_expires_unique'
+ remove_index :rules, name: 'index_rules_on_network_policy_unique'
+ remove_index :rules, name: 'index_rules_on_source_expires'
+ remove_index :rules, name: 'index_rules_on_policy_expires'
+ end
+
+ private
+
+ def cleanup_duplicate_rules
+ # Clean up duplicates for policy rules with the same expiration time
+ duplicate_sql = <<~SQL
+ WITH ranked_rules AS (
+ SELECT id,
+ ROW_NUMBER() OVER (
+ PARTITION BY network_range_id, waf_action, waf_policy_id, expires_at
+ ORDER BY created_at DESC
+ ) as rn
+ FROM rules
+ WHERE source = 'policy'
+ )
+ DELETE FROM rules
+ WHERE id IN (SELECT id FROM ranked_rules WHERE rn > 1)
+ SQL
+
+ execute duplicate_sql
+ Rails.logger.info "Cleaned up duplicate policy rules with same expiration times"
+ end
+end