diff --git a/fail2ban/jail_blocks.cgi b/fail2ban/jail_blocks.cgi
index e8d14df8a..9106641d6 100644
--- a/fail2ban/jail_blocks.cgi
+++ b/fail2ban/jail_blocks.cgi
@@ -33,7 +33,7 @@ while(<$fh>) {
close($fh);
my $pagination_opts = \%in;
-$pagination_opts->{'_form-exports'} = { 'jail' => $jail };
+$pagination_opts->{'_form-exports'} = { 'jail' => $jail, 'colspan' => 4 };
if (@jail_blocks) {
my $pagination = &ui_paginations(\@jail_blocks, $pagination_opts);
my @links = ( &select_all_link("ip"),
@@ -51,7 +51,7 @@ if (@jail_blocks) {
}
}
else {
- print &ui_columns_row([&text('status_jail_nosearchrs', $in{'search'})], ['colspan="4" align="center"']);
+ print $pagination->{'search-no-results'};
}
print &ui_columns_end();
print $pagination->{'paginator-form-data'};
diff --git a/fail2ban/lang/en b/fail2ban/lang/en
index 700e4244e..27bb96337 100644
--- a/fail2ban/lang/en
+++ b/fail2ban/lang/en
@@ -189,7 +189,6 @@ status_jail_unblock=Unblock Selected Jails
status_jail_unblock_ips=Unblock Selected IP
status_jail_noactive=There are no active jails enabled yet.
status_jail_noactiveips=There are no blocked entries in jail $1 found.
-status_jail_nosearchrs=There are no results matching $1 query
status_err_set=Failed set action
status_err_unblock=Failed to unblock action
status_err_nojail=No jails have been selected
diff --git a/gray-theme/unauthenticated/gray-theme.css b/gray-theme/unauthenticated/gray-theme.css
index 47d0b387a..4f5492c09 100644
--- a/gray-theme/unauthenticated/gray-theme.css
+++ b/gray-theme/unauthenticated/gray-theme.css
@@ -575,4 +575,23 @@ body > .mode > b[data-mode="server-manager"] > a > .ff-cloudmin {
display: inline-block;
max-width: 80%;
width: 80%;
+}
+.ui_link_pagination.disabled {
+ filter: grayscale(1) opacity(0.6);
+ pointer-events: none;
+}
+
+.ui_form_elements_wrapper_search {
+ float:right;
+ margin-bottom: 3px;
+}
+
+.ui_form_elements_wrapper_paginator {
+ float:right;
+ margin-top: 3px;
+}
+.ui_form_elements_wrapper_paginator .ui_textbox {
+ border: 1px dashed #0b46ab;
+ padding-right: 2px;
+ text-align: center;
}
\ No newline at end of file
diff --git a/lang/en b/lang/en
index 134dd912e..4cc3cf3cd 100644
--- a/lang/en
+++ b/lang/en
@@ -380,6 +380,10 @@ nice_size_kB=kB
nice_size_kiB=KiB
nice_size_b=bytes
+paginator_showing_start=Showing $1 to $2 of $3 items on page
+paginator_showing_end=of $1 pages
+paginator_nosearchrs=There are no results matching $1 query
+
langauto_include=Include machine translations
file_truncated_message=fetched $1 of data, truncated $2 out of $3
diff --git a/ui-lib.pl b/ui-lib.pl
index 1f1507d36..8957eeba0 100755
--- a/ui-lib.pl
+++ b/ui-lib.pl
@@ -2820,31 +2820,25 @@ return &theme_ui_paginations(@_)
my ($arr, $opts) = @_;
my %rv;
-my @arr = @{$arr};
+my @arr = @{$arr};
my ($script_name) = $0 =~ /([^\/]*\.cgi)$/;
my $items_per_page = int($tconfig{'paginate'}) || int($opts->{'paginate'}) || 20;
my $curent_page = int($opts->{'page'}) || 1;
-my $search_term = $opts->{'search'};
+my $search_term = &un_urlize($opts->{'search'});
+my $search_placeholder = $opts->{'search_placeholder'} || $text{'ui_searchok'};
my $pagination_target = $opts->{'pagination_target'} || $script_name;
my $pagination_action = $opts->{'pagination_action'} || "post";
my $search_target = $opts->{'search_target'} || $script_name;
my $search_action = $opts->{'search_action'} || "post";
-my $search_placeholder = $opts->{'search_placeholder'} || $text{'ui_searchok'};
my $paginator_wrap_class = $opts->{'paginator_wrap_class'} || "ui_form_elements_wrapper_paginator";
-my $paginator_wrap_style = $opts->{'paginator_wrap_style'} || "style='float:right;margin-top: 3px;'";
my $search_wrap_class = $opts->{'search_wrap_class'} || "ui_form_elements_wrapper_search";
-my $search_wrap_style = $opts->{'search_wrap_style'} || "style='float:right;margin-bottom: 3px;'";
my $link_page_cls = $opts->{'paginator_link_class'} || 'ui_link_pagination';
my $link_search_cls = $opts->{'paginator_textbox_class'} || 'ui_textbox_pagination';
+my $text_showing_cls = $opts->{'paginator_text_showing'} || 'ui_showing_items';
my $exported_form = $opts->{'_form-exports'};
-my $exported_form_query = "";
-if ($exported_form) {
- foreach (keys %{$exported_form}) {
- $exported_form_query .= "&"."$_=$exported_form->{$_}";
- }
- }
+my $ui_column_colspan = int($exported_form->{'colspan'} || 2);
-# If we have a search string filter content
+# If we have a search string filter existing content
if (ref($arr) eq 'ARRAY' && $arr->[0]) {
if ($search_term) {
my @sarr;
@@ -2857,18 +2851,21 @@ if (ref($arr) eq 'ARRAY' && $arr->[0]) {
} @arr;
@arr = @sarr;
}
+ # Can pagination be done automatically
+ # depending on the client screen height?
my $items_per_page =
$tconfig{'paginate-noauto'} ?
$items_per_page :
- int($opts->{'client_height'}) ?
- int($ENV{'HTTP_X_CLIENT_HEIGHT'}) ||
- int($opts->{'client_height'} / 35) : $items_per_page;
+ (int($ENV{'HTTP_X_CLIENT_HEIGHT'}) ||
+ (int($opts->{'client_height'}) ?
+ int($opts->{'client_height'} / 37) : $items_per_page));
+ # Pagination
my $totals_items_original = scalar(@arr);
my $total_pages = ceil(($totals_items_original) / $items_per_page);
my $total_pages_length = length($total_pages);
# Return pagination jumper only
- # if there us more than one page
+ # if there is more than one page
if ($total_pages > 1) {
my $totals_items_spliced = $totals_items_original;
my $start_page_with = $curent_page * $items_per_page;
@@ -2886,61 +2883,116 @@ if (ref($arr) eq 'ARRAY' && $arr->[0]) {
@arr = splice(@arr, $splice_start, $splice_end);
$totals_items_spliced = scalar(@arr);
+ #
# Pagination jumper
+ #
my $paginator_id = 'paginator-form';
+ # As clicking on pagination arrows send
+ # current screen height to the server
my $screenHeightGetter =
"onclick='this.href=this.href+\"&client_height=\"+document.documentElement.clientHeight'";
+ # Paginator form
$rv{$paginator_id} =
- &ui_form_start($pagination_target, $pagination_action, undef, "id='$paginator_id'");
+ &ui_form_start($pagination_target, "e_escape($pagination_action), undef, "id='$paginator_id'");
$rv{$paginator_id} .= &ui_form_end();
+ # Paginator form data
$rv{"$paginator_id-data"} = &ui_hidden("search", $search_term, $paginator_id)
if ($search_term);
$rv{"$paginator_id-data"} .= &ui_hidden("paginate", $items_per_page, $paginator_id);
+ # Calculate showing start and range numbers
+ my $current_showing_start =
+ $curent_page == 1 ? 1 : ($items_per_page * $curent_page + 1) - $items_per_page;
+ my $current_showing_range =
+ $current_showing_start + $items_per_page > $totals_items_original ?
+ $totals_items_original : $current_showing_start + $items_per_page;
+ # Showing items range selector text
$rv{"$paginator_id-data"} .=
- &ui_link("?page=$curent_page_prev&search=$search_term&paginate=$items_per_page$exported_form_query",
- ' ⏴ ', "$link_page_cls ${link_page_cls}_left$page_prev_disabled", $screenHeightGetter);
+ "@{[
+ &text('paginator_showing_start', $current_showing_start,
+ $current_showing_range, $totals_items_original) ]} ";
+ # Dynamically add external form elements to arrow links
+ my $exported_form_query = "";
+ if ($exported_form) {
+ foreach (keys %{$exported_form}) {
+ $exported_form_query .= "&"."$_=@{[&urlize($exported_form->{$_})]}";
+ }
+ }
+ #
+ # Arrow links
+ #
+ my $search_term_urlize = &urlize($search_term);
+ my $curent_page_prev_urlize = &urlize($curent_page_prev);
+ my $curent_page_next_urlize = &urlize($curent_page_next);
+ my $items_per_page_urlize = &urlize($items_per_page);
+ my $total_pages_html_escape = &html_escape($total_pages);
+ # Arrow link left
+ $rv{"$paginator_id-data"} .=
+ &ui_link("?page=$curent_page_prev_urlize&search=$search_term_urlize&paginate=$items_per_page_urlize$exported_form_query",
+ ' ⏴ ',
+ "@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_left$page_prev_disabled",
+ $screenHeightGetter);
+ # Page number input selector
$rv{"$paginator_id-data"} .=
&ui_textbox("page", $curent_page, $total_pages_length, undef, $total_pages_length,
- "data-class='$link_search_cls' form='$paginator_id'");
- $rv{"$paginator_id-data"} .= " $text{'ui_of'} $total_pages";
+ "data-class='@{["e_escape($link_search_cls)]}' form='$paginator_id'");
+ # Out of pages text
$rv{"$paginator_id-data"} .=
- &ui_link("?page=$curent_page_next&search=$search_term&paginate=$items_per_page$exported_form_query",
- ' ▸ ', "$link_page_cls ${link_page_cls}_right$page_next_disabled", $screenHeightGetter);
+ " @{[&text('paginator_showing_end', $total_pages_html_escape)]}";
+ # Arrow link right
+ $rv{"$paginator_id-data"} .=
+ &ui_link("?page=$curent_page_next_urlize&search=$search_term_urlize&paginate=$items_per_page_urlize$exported_form_query",
+ ' ▸ ',
+ "@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_right$page_next_disabled",
+ $screenHeightGetter);
+ # Dynamically adding external form elements
if ($exported_form) {
foreach (keys %{$exported_form}) {
$rv{"$paginator_id-data"} .= &ui_hidden($_, $exported_form->{$_}, $paginator_id);
}
}
- # $rv{"$paginator_id-data"} .= &ui_submit(undef, undef, undef, "form='$paginator_id'");
$rv{"$paginator_id-data"} =
&ui_form_elements_wrapper($rv{"$paginator_id-data"}, $paginator_id,
- $paginator_wrap_class, $paginator_wrap_style)
+ "e_escape($paginator_wrap_class))
}
-
+ #
# Search form
+ #
if ($total_pages > 1 || $search_term) {
- my $search_id = 'search-form';
- $rv{$search_id} = &ui_form_start($search_target, $search_action, undef, "id='$search_id'");
+ my $search_id = 'search-form';
+ my $search_data = "$search_id-data";
+
+ # Paginator search form
+ $rv{$search_id} = &ui_form_start($search_target, "e_escape($search_action), undef, "id='$search_id'");
$rv{$search_id} .= &ui_form_end();
- $rv{"$search_id-data"} .= &ui_hidden("paginate", $items_per_page, $search_id);
- $rv{"$search_id-data"} .= &ui_hidden("page", 1, $search_id);
+
+ # Paginator search form data
+ $rv{$search_data} .= &ui_hidden("paginate", $items_per_page, $search_id);
+ $rv{$search_data} .= &ui_hidden("page", 1, $search_id);
my $search_placeholder_length = length($search_placeholder);
- $rv{"$search_id-data"} .=
- &ui_textbox("search", $search_term, $search_placeholder_length < 12 ? 12 : $search_placeholder_length,
- undef, undef, "data-class='${link_search_cls}_search' placeholder='$search_placeholder' form='$search_id'");
- # $rv{"$search_id-data"} .= &ui_submit($text{'ui_searchok'}, undef, undef, "form='$search_id'");
- $rv{"$search_id-data"} .=
+ $search_placeholder_length < 12 ? 12 : $search_placeholder_length;
+ # Search box
+ $rv{$search_data} .=
+ &ui_textbox("search", $search_term, $search_placeholder_length, undef, undef,
+ "data-class='@{["e_escape($link_search_cls)]}_search' placeholder='@{["e_escape($search_placeholder)]}' form='$search_id'");
+ # Search reset using JS
+ $rv{$search_data} .=
&ui_reset($text{'reset'}, undef,
"onclick='document.getElementById(\"$search_id\").search.value = \"\";".
"document.getElementById(\"$search_id\").submit()'");
+ # Dynamically adding external form elements
if ($exported_form) {
foreach (keys %{$exported_form}) {
- $rv{"$search_id-data"} .= &ui_hidden($_, $exported_form->{$_}, $search_id);
+ $rv{$search_data} .= &ui_hidden($_, $exported_form->{$_}, $search_id);
}
}
- $rv{"$search_id-data"} = &ui_form_elements_wrapper($rv{"$search_id-data"}, $search_id, $search_wrap_class, $search_wrap_style)
+ $rv{$search_data} = &ui_form_elements_wrapper($rv{$search_data}, $search_id, "e_escape($search_wrap_class));
}
+ # Search no results
+ $rv{"search-no-results"} =
+ &ui_columns_row([&text('paginator_nosearchrs', &html_escape($search_term))],
+ ['colspan="'.$ui_column_colspan.'" align="center"']);
+
# After all forms were added run JavaScript script to inject
# client height information into each form, which will enable
# automatic calculation of items per page to fit properly