Files
webmin/nftables/active_table.cgi
Ilia Ross 3c9d53109b Fix to rework nftables management around saved tables
Rework the nftables module so Webmin manages its saved nftables configuration as the source of truth instead of directly editing the live ruleset. Add an active ruleset view for inspecting live tables and importing copies into Webmin-managed config if needed, track managed and imported tables with metadata, and prevent externally managed tables from being overwritten during apply.

Co-authored-by: Copilot <copilot@github.com>
2026-05-02 19:02:37 +02:00

109 lines
3.4 KiB
Perl
Executable File

#!/usr/bin/perl
# active_table.cgi
# Show a read-only active nftables table
require './nftables-lib.pl'; ## no critic
use strict;
use warnings;
our (%in, %text);
ReadParse();
error_setup($text{'active_table_err'});
my ($tables, $err) = get_active_nftables_save();
error(text('active_failed', $err)) if ($err);
my $table;
foreach my $t (@$tables) {
if ($t->{'family'} eq $in{'family'} && $t->{'name'} eq $in{'name'}) {
$table = $t;
last;
}
}
$table || error($text{'active_table_notable'});
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);
print ui_table_start($text{'active_table_summary'}, "width=100%", 2);
print ui_table_row($text{'active_table'}, html_escape(nft_table_spec($table)));
print ui_table_row($text{'active_flags'}, html_escape($table->{'flags'} || "-"));
print ui_table_row($text{'active_status'}, $text{'active_'.$status_key});
print ui_table_end();
if (!$is_saved) {
print ui_buttons_start();
print ui_buttons_row(
"import_table.cgi?family=".urlize($table->{'family'}).
"&name=".urlize($table->{'name'}),
$text{'active_import'}, $text{'active_importdesc'});
print ui_buttons_end();
}
my ($chains_html, $sets_html);
$chains_html .= ui_columns_start(
[ $text{'index_chain_col'}, $text{'index_type'}, $text{'index_hook'},
$text{'index_priority'}, $text{'index_policy_col'}, $text{'index_rules'} ],
100);
foreach my $c (sort keys %{$table->{'chains'}}) {
my $chain_def = $table->{'chains'}->{$c} || { };
my $policy = $chain_def->{'policy'};
my $policy_label = $policy ?
($text{'index_policy_'.lc($policy)} || uc($policy)) : "-";
my @rules = grep { $_->{'chain'} eq $c } @{$table->{'rules'}};
my $rules_html = @rules ?
ui_tag('div',
join("", map {
ui_tag('div', describe_rule($_),
{ 'class' => 'nftables_rule_text' })
} @rules),
{ 'class' => 'nftables_rules_list',
'style' => 'display: grid; row-gap: 0.25em;' }) :
ui_tag('i', $text{'index_rules_none'});
$chains_html .= ui_columns_row([
html_escape($c),
html_escape($chain_def->{'type'} || "-"),
html_escape($chain_def->{'hook'} || "-"),
defined($chain_def->{'priority'}) ?
html_escape($chain_def->{'priority'}) : "-",
html_escape($policy_label),
$rules_html,
]);
}
$chains_html .= ui_columns_end();
$sets_html .= ui_columns_start(
[ $text{'index_set_name'}, $text{'index_set_type'},
$text{'index_set_flags'}, $text{'index_set_elements'} ], 100);
if ($table->{'sets'} && ref($table->{'sets'}) eq 'HASH') {
foreach my $s (sort keys %{$table->{'sets'}}) {
my $set = $table->{'sets'}->{$s} || { };
$sets_html .= ui_columns_row([
html_escape($s),
html_escape($set->{'type'} || "-"),
html_escape($set->{'flags'} || "-"),
html_escape(set_elements_summary($set)),
]);
}
}
$sets_html .= ui_columns_end();
my @tabs = (
[ 'chains', $text{'index_tab_chains'} ],
[ 'sets', $text{'index_tab_sets'} ],
);
my $tab = $in{'view'} && $in{'view'} eq 'sets' ? 'sets' : 'chains';
print ui_hr();
print ui_tabs_start(\@tabs, "view", $tab, 1);
print ui_tabs_start_tab("view", "chains");
print $chains_html;
print ui_tabs_end_tab();
print ui_tabs_start_tab("view", "sets");
print $sets_html;
print ui_tabs_end_tab();
print ui_tabs_end(1);
ui_print_footer("active.cgi", $text{'active_return'});