From 31d4b6dfd65cb74a083511aae54a0f2e245615c2 Mon Sep 17 00:00:00 2001 From: Joe Cooper Date: Mon, 2 Feb 2026 18:07:42 -0600 Subject: [PATCH] Re-order rules support --- images/icon.gif | Bin 0 -> 2918 bytes index.cgi | 29 +++++++++++++++++++++------- lang/en | 5 +++++ move_rule.cgi | 40 ++++++++++++++++++++++++++++++++++++++ nftables-lib.pl | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ t/run-tests.t | 24 +++++++++++++++++++++++ 6 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 images/icon.gif create mode 100755 move_rule.cgi diff --git a/images/icon.gif b/images/icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..650a15379bd59079f5b03223c3ae11d5cc541764 GIT binary patch literal 2918 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 literal 0 HcmV?d00001 diff --git a/index.cgi b/index.cgi index 605973ffc..af3797db3 100755 --- a/index.cgi +++ b/index.cgi @@ -87,21 +87,36 @@ if (!@tables) { my @rules = grep { $_->{'chain'} eq $c } @{$curr->{'rules'}}; my $rules_html_row; if (@rules) { - my @rows; + my $ri = 0; + $rules_html_row = "\n"; foreach my $r (@rules) { my $desc = &describe_rule($r); - push(@rows, &ui_link( + my $rule_link = &ui_link( "edit_rule.cgi?table=$in{'table'}&chain=". &urlize($c)."&idx=$r->{'index'}", - $desc)); + $desc); + my $move = &ui_up_down_arrows( + "move_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&idx=$r->{'index'}&dir=up", + "move_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&idx=$r->{'index'}&dir=down", + $ri > 0, + $ri < $#rules); + $rules_html_row .= "". + "\n"; + $ri++; } - $rules_html_row = join("
", @rows); + $rules_html_row .= "\n"; + $rules_html_row .= "
$rule_link$move
". + &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&new=1", $text{'index_radd'}). + "
"; } else { $rules_html_row = "$text{'index_rules_none'}"; + $rules_html_row .= "
". + &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&new=1", $text{'index_radd'}); } - $rules_html_row .= "
". - &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". - &urlize($c)."&new=1", $text{'index_radd'}); my $actions_html = &ui_link("edit_chain.cgi?table=$in{'table'}&chain=". diff --git a/lang/en b/lang/en index 838a0a52d..4ad0b702f 100644 --- a/lang/en +++ b/lang/en @@ -177,3 +177,8 @@ save_failed=Failed to save rule:
$1
save_raw_empty=Raw rule cannot be empty. save_raw_multiline=Raw rule must be a single line. save_invalid_rule=Raw rule is invalid: $1 +move_err=Failed to move rule +move_failed=Failed to move rule:
$1
+move_notable=No such table selected +move_nochain=No such chain selected +move_norule=No such rule selected diff --git a/move_rule.cgi b/move_rule.cgi new file mode 100755 index 000000000..fef1ededb --- /dev/null +++ b/move_rule.cgi @@ -0,0 +1,40 @@ +#!/usr/bin/perl +# move_rule.cgi +# Move a rule up or down within a chain + +require './nftables-lib.pl'; +use strict; +use warnings; +our (%in, %text); +&ReadParse(); +&error_setup($text{'move_err'}); + +my @tables = &get_nftables_save(); +my $table = $tables[$in{'table'}]; +$table || &error($text{'move_notable'}); + +my $chain = $in{'chain'}; +$chain || &error($text{'move_nochain'}); + +my $dir = $in{'dir'}; +$dir = '' if (!defined($dir)); + +my $idx = $in{'idx'}; +$idx =~ /^\d+$/ || &error($text{'move_norule'}); + +my $rv = &move_rule_in_chain($table, $chain, $idx, $dir); +if (!defined($rv)) { + &error($text{'move_norule'}); +} + +if ($rv) { + my $err = &save_configuration(@tables); + &error(&text('move_failed', $err)) if ($err); + &webmin_log("move", "rule", undef, + { 'table' => $table->{'name'}, + 'family' => $table->{'family'}, + 'chain' => $chain, + 'dir' => $dir }); +} + +&redirect("index.cgi?table=$in{'table'}"); diff --git a/nftables-lib.pl b/nftables-lib.pl index bc3d1a352..7b567ced7 100644 --- a/nftables-lib.pl +++ b/nftables-lib.pl @@ -191,6 +191,56 @@ if (defined($type) || defined($hook) || defined($priority) || defined($policy)) return 1; } +sub move_rule_in_chain +{ +my ($table, $chain, $idx, $dir) = @_; +return undef if (!defined($table) || ref($table) ne 'HASH'); +return undef if (!defined($idx) || $idx !~ /^\d+$/); +return undef if (!defined($chain) || $chain eq ''); +return undef if (!$table->{'rules'} || ref($table->{'rules'}) ne 'ARRAY'); +return undef if ($idx > $#{$table->{'rules'}}); +my $rule = $table->{'rules'}->[$idx]; +return undef if (!$rule || $rule->{'chain'} ne $chain); + +my @chain_idxs; +for (my $i = 0; $i < @{$table->{'rules'}}; $i++) { + my $r = $table->{'rules'}->[$i]; + next if (!$r || ref($r) ne 'HASH'); + push(@chain_idxs, $i) if ($r->{'chain'} && $r->{'chain'} eq $chain); +} +my $pos; +for (my $i = 0; $i <= $#chain_idxs; $i++) { + if ($chain_idxs[$i] == $idx) { + $pos = $i; + last; + } +} +return undef if (!defined($pos)); + +my $swap; +if ($dir eq 'up') { + return 0 if ($pos == 0); + $swap = $chain_idxs[$pos-1]; +} +elsif ($dir eq 'down') { + return 0 if ($pos == $#chain_idxs); + $swap = $chain_idxs[$pos+1]; +} +else { + return undef; +} + +($table->{'rules'}->[$idx], $table->{'rules'}->[$swap]) = + ($table->{'rules'}->[$swap], $table->{'rules'}->[$idx]); + +for (my $i = 0; $i < @{$table->{'rules'}}; $i++) { + my $r = $table->{'rules'}->[$i]; + $r->{'index'} = $i if ($r && ref($r) eq 'HASH'); +} + +return 1; +} + sub format_addr_expr { my ($dir, $rule) = @_; diff --git a/t/run-tests.t b/t/run-tests.t index 2e79c8b12..813a128ab 100755 --- a/t/run-tests.t +++ b/t/run-tests.t @@ -135,4 +135,28 @@ ok(!&validate_chain_base('filter', 'input', undef, 'accept'), ok(&validate_chain_base(undef, undef, undef, undef), 'chain base none set valid'); +my $table_move = { + rules => [ + { chain => 'input', index => 0, text => 'r0' }, + { chain => 'input', index => 1, text => 'r1' }, + { chain => 'forward', index => 2, text => 'r2' }, + { chain => 'input', index => 3, text => 'r3' }, + ], +}; +ok(&move_rule_in_chain($table_move, 'input', 1, 'down'), + 'move rule down returns true'); +is($table_move->{rules}->[1]->{text}, 'r3', 'rule moved down in array'); +is($table_move->{rules}->[3]->{text}, 'r1', 'rule swapped down in array'); +is($table_move->{rules}->[1]->{index}, 1, 'moved rule index updated'); +is($table_move->{rules}->[3]->{index}, 3, 'swapped rule index updated'); + +my $table_move2 = { + rules => [ + { chain => 'input', index => 0, text => 'r0' }, + { chain => 'input', index => 1, text => 'r1' }, + ], +}; +is(&move_rule_in_chain($table_move2, 'input', 0, 'up'), 0, + 'top rule cannot move up'); + done_testing();