From 8c15fc9fdb79891f34476460a739561a66c91cab Mon Sep 17 00:00:00 2001 From: iliajie Date: Tue, 9 May 2023 13:15:33 +0300 Subject: [PATCH] =?UTF-8?q?Fix=20escapes=20;=20add=20support=20to=20displa?= =?UTF-8?q?y=20items=20on=20page=20e.g.:=20Showing=2043=20to=2064=20of=208?= =?UTF-8?q?3=20items=20on=20page=20=20=E2=8F=B4=203=20of=204=20pages=20?= =?UTF-8?q?=E2=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fail2ban/jail_blocks.cgi | 4 +- fail2ban/lang/en | 1 - gray-theme/unauthenticated/gray-theme.css | 19 ++++ lang/en | 4 + ui-lib.pl | 126 +++++++++++++++------- 5 files changed, 114 insertions(+), 40 deletions(-) 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