diff --git a/nftables/active.cgi b/nftables/active.cgi
index e8472dc68..ed728396b 100755
--- a/nftables/active.cgi
+++ b/nftables/active.cgi
@@ -7,7 +7,8 @@ use strict;
use warnings;
our (%text);
-ui_print_header(undef, $text{'active_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'active_title'}, "", "intro", 1, 1,
+ undef, restart_button());
my ($tables, $err) = get_active_nftables_save();
if ($err) {
diff --git a/nftables/active_table.cgi b/nftables/active_table.cgi
index 082aada68..5065161fa 100755
--- a/nftables/active_table.cgi
+++ b/nftables/active_table.cgi
@@ -24,7 +24,8 @@ my @saved_tables = get_nftables_save();
my $status_key = active_table_status($table, \@saved_tables);
my $is_saved = table_is_webmin_managed($table, \@saved_tables);
-ui_print_header(undef, $text{'active_table_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'active_table_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print ui_table_start($text{'active_table_summary'}, "width=100%", 2);
print ui_table_row($text{'active_table'}, html_escape(nft_table_spec($table)));
diff --git a/nftables/clear_table.cgi b/nftables/clear_table.cgi
index cff049737..76957e6a4 100755
--- a/nftables/clear_table.cgi
+++ b/nftables/clear_table.cgi
@@ -30,7 +30,8 @@ if ($in{'confirm'}) {
return;
}
-ui_print_header(undef, $text{'clear_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'clear_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print "
\n";
print ui_form_start("clear_table.cgi");
print ui_hidden("family", $table->{'family'});
diff --git a/nftables/create_table.cgi b/nftables/create_table.cgi
index 0456cc481..5a7122d9c 100755
--- a/nftables/create_table.cgi
+++ b/nftables/create_table.cgi
@@ -50,7 +50,8 @@ if ($in{'create'}) {
return;
}
-ui_print_header(undef, $text{'create_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'create_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print ui_form_start("create_table.cgi");
print ui_hidden("create", 1);
diff --git a/nftables/delete_table.cgi b/nftables/delete_table.cgi
index a0470d69e..70a56435b 100755
--- a/nftables/delete_table.cgi
+++ b/nftables/delete_table.cgi
@@ -29,18 +29,21 @@ else {
$table || error($text{'delete_notable'});
if ($in{'confirm'}) {
+ my $needs_apply = needs_config_restart();
splice(@tables, $table_idx, 1);
my $err = delete_table_configuration($table, @tables);
error(text('delete_failed', $err)) if ($err);
$err = delete_active_table($table);
error(text('delete_failed', $err)) if ($err);
+ restart_last_restart_time() if (!$needs_apply);
webmin_log("delete", "table", $table->{'name'},
{ 'family' => $table->{'family'} });
redirect("index.cgi");
return;
}
-ui_print_header(undef, $text{'delete_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'delete_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print "\n";
print ui_form_start("delete_table.cgi");
print ui_hidden("table", $table_idx);
diff --git a/nftables/edit_chain.cgi b/nftables/edit_chain.cgi
index f7580b69e..41fde4db6 100644
--- a/nftables/edit_chain.cgi
+++ b/nftables/edit_chain.cgi
@@ -17,12 +17,14 @@ my $chain_name = "";
my $is_new = $in{'new'} ? 1 : 0;
if ($is_new) {
- ui_print_header(undef, $text{'chain_title_new'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'chain_title_new'}, "", "intro", 1, 1,
+ undef, restart_button());
} else {
$chain_name = $in{'chain'};
$chain = $table->{'chains'}->{$chain_name};
$chain || error($text{'chain_nochain'});
- ui_print_header(undef, $text{'chain_title_edit'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'chain_title_edit'}, "", "intro", 1, 1,
+ undef, restart_button());
}
my @type_opts = (
@@ -84,4 +86,3 @@ if (window.addEventListener) {
EOF
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
-
diff --git a/nftables/edit_rule.cgi b/nftables/edit_rule.cgi
index f531a2964..fc849b55b 100755
--- a/nftables/edit_rule.cgi
+++ b/nftables/edit_rule.cgi
@@ -47,10 +47,12 @@ sub split_multi_value
}
if ($in{'new'}) {
- ui_print_header(undef, $text{'edit_title_new'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'edit_title_new'}, "", "intro", 1, 1,
+ undef, restart_button());
$rule = { 'chain' => $in{'chain'} };
} else {
- ui_print_header(undef, $text{'edit_title_edit'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'edit_title_edit'}, "", "intro", 1, 1,
+ undef, restart_button());
$rule = $table->{'rules'}->[$in{'idx'}];
}
if ($table && $rule->{'chain'}) {
diff --git a/nftables/edit_set.cgi b/nftables/edit_set.cgi
index bccd95787..c34caff5b 100755
--- a/nftables/edit_set.cgi
+++ b/nftables/edit_set.cgi
@@ -17,13 +17,15 @@ my $set_name = "";
my $is_new = $in{'new'} ? 1 : 0;
if ($is_new) {
- ui_print_header(undef, $text{'set_title_new'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'set_title_new'}, "", "intro", 1, 1,
+ undef, restart_button());
}
else {
$set_name = $in{'set'};
$set = $table->{'sets'}->{$set_name};
$set || error($text{'set_noset'});
- ui_print_header(undef, $text{'set_title_edit'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'set_title_edit'}, "", "intro", 1, 1,
+ undef, restart_button());
}
my $elements_text = set_elements_text($set);
diff --git a/nftables/import_table.cgi b/nftables/import_table.cgi
index 99d012d36..3dc5a3d26 100755
--- a/nftables/import_table.cgi
+++ b/nftables/import_table.cgi
@@ -60,7 +60,8 @@ if ($in{'import'}) {
return;
}
-ui_print_header(undef, $text{'import_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'import_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print ui_form_start("import_table.cgi");
print ui_hidden("family", $source->{'family'});
diff --git a/nftables/index.cgi b/nftables/index.cgi
index 0bdae9aab..36f36ab56 100755
--- a/nftables/index.cgi
+++ b/nftables/index.cgi
@@ -9,7 +9,8 @@ our (%in, %text, %config);
ReadParse();
my $partial = $in{'partial'};
if (!$partial) {
- ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1);
+ ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1,
+ undef, restart_button());
}
# Check for nft command
@@ -279,7 +280,7 @@ print $rules_html;
if (@tables) {
print ui_hr();
print ui_buttons_start();
- print ui_buttons_row("apply.cgi", $text{'index_apply'}, $text{'index_applydesc'});
+ print ui_buttons_row("restart.cgi", $text{'index_apply'}, $text{'index_applydesc'});
print ui_buttons_row("active.cgi", $text{'index_active'}, $text{'index_activedesc'});
print ui_buttons_row("setup.cgi", $text{'index_setup'}, $text{'index_setupdesc'});
print ui_buttons_end();
diff --git a/nftables/lang/en b/nftables/lang/en
index 5e99a4e03..4b837c2f1 100644
--- a/nftables/lang/en
+++ b/nftables/lang/en
@@ -50,6 +50,7 @@ index_cdeletesel=Delete Selected Chains
index_cmovesel=Move Selected
index_radd=Add Rule
index_apply=Apply Configuration
+index_apply_changes=Apply Changes
index_applydesc=Click this button to replace the saved Webmin-managed tables in the active nftables ruleset.
index_active=View Active Ruleset
index_activedesc=View active nftables tables and import copies into Webmin's saved configuration.
diff --git a/nftables/nftables-lib.pl b/nftables/nftables-lib.pl
index fcd2b56e0..d64428852 100644
--- a/nftables/nftables-lib.pl
+++ b/nftables/nftables-lib.pl
@@ -5,8 +5,63 @@ BEGIN { push(@INC, ".."); }; ## no critic
use WebminCore;
use strict;
use warnings;
-our (%config, $module_config_directory);
+our (%config, $module_config_directory, $module_var_directory);
+our ($last_config_change_flag, $last_restart_time_flag);
init_config();
+$last_config_change_flag = $module_var_directory."/config-flag";
+$last_restart_time_flag = $module_var_directory."/restart-flag";
+
+# restart_button()
+# Returns HTML for the header apply button
+sub restart_button
+{
+my @tables = get_nftables_save();
+return "" if (!@tables);
+my $args = "redir=".urlize(this_url());
+my $needs = needs_config_restart();
+my $apply = text('index_apply_changes');
+my $label = $needs ? "$apply" : $apply;
+my $url = "restart.cgi?$args";
+$url .= "&newconfig=1" if ($needs);
+return ui_link($url, $label);
+}
+
+# this_url()
+# Returns the URL in the nftables module for the current script
+sub this_url
+{
+my $url = $ENV{'SCRIPT_NAME'} || "";
+my $query = $ENV{'QUERY_STRING'} || "";
+$url .= "?$query" if ($query ne "");
+return $url;
+}
+
+# update_last_config_change()
+# Updates the flag file indicating when the saved config was changed
+sub update_last_config_change
+{
+open_tempfile(my $fh, ">$last_config_change_flag", 0, 1);
+close_tempfile($fh);
+}
+
+# restart_last_restart_time()
+# Updates the flag file indicating when the saved config was applied
+sub restart_last_restart_time
+{
+open_tempfile(my $fh, ">$last_restart_time_flag", 0, 1);
+close_tempfile($fh);
+}
+
+# needs_config_restart()
+# Returns 1 if saved config changes still need to be applied
+sub needs_config_restart
+{
+my @cst = stat($last_config_change_flag);
+my @rst = stat($last_restart_time_flag);
+return 0 if (!@cst);
+return 1 if (!@rst);
+return $cst[9] > $rst[9] ? 1 : 0;
+}
# get_nft_command()
# Returns the configured nft command path, or finds it in PATH
@@ -1198,6 +1253,7 @@ open_tempfile(my $fh, ">$file");
print_tempfile($fh, $out);
close_tempfile($fh);
sync_managed_metadata(@tables);
+update_last_config_change();
return;
}
@@ -1294,6 +1350,7 @@ unlink_file($tmp);
if ($?) {
return "$out
";
}
+restart_last_restart_time();
return;
}
diff --git a/nftables/rename_chain.cgi b/nftables/rename_chain.cgi
index 4c82e21c3..13ef7fe26 100644
--- a/nftables/rename_chain.cgi
+++ b/nftables/rename_chain.cgi
@@ -15,7 +15,8 @@ $table || error($text{'chain_notable'});
my $chain = $table->{'chains'}->{$in{'chain'}};
$chain || error($text{'chain_nochain'});
-ui_print_header(undef, $text{'rename_chain_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'rename_chain_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print ui_form_start("save_chain.cgi");
print ui_hidden("table", $in{'table'});
print ui_hidden("rename", 1);
@@ -30,4 +31,3 @@ print ui_table_end();
print ui_form_end([ [ undef, $text{'rename_chain_ok'} ] ]);
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
-
diff --git a/nftables/apply.cgi b/nftables/restart.cgi
similarity index 60%
rename from nftables/apply.cgi
rename to nftables/restart.cgi
index 4fa246ec1..0ae1f1886 100755
--- a/nftables/apply.cgi
+++ b/nftables/restart.cgi
@@ -1,6 +1,6 @@
#!/usr/bin/perl
-# apply.cgi
-# Apply the current configuration
+# restart.cgi
+# Apply saved nftables configuration from the header action
require './nftables-lib.pl'; ## no critic
use strict;
@@ -12,4 +12,5 @@ error_setup($text{'apply_err'});
my $err = apply_restore();
error($err) if ($err);
-redirect("index.cgi");
+webmin_log("apply");
+redirect($in{'redir'} || "index.cgi");
diff --git a/nftables/setup.cgi b/nftables/setup.cgi
index 0b0f287ab..8c6867361 100644
--- a/nftables/setup.cgi
+++ b/nftables/setup.cgi
@@ -47,7 +47,8 @@ if ($in{'action'} eq 'create') {
return;
}
-ui_print_header(undef, $text{'setup_title'}, "", "intro", 1, 1);
+ui_print_header(undef, $text{'setup_title'}, "", "intro", 1, 1,
+ undef, restart_button());
print ui_form_start("setup.cgi");
print ui_hidden("action", "create");