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