1010 session ,
1111 make_response ,
1212 abort ,
13+ redirect ,
14+ url_for ,
1315)
16+
1417from markupsafe import Markup
1518from flowapp import models , validators , flowspec
1619from flowapp .auth import auth_required
@@ -86,18 +89,15 @@ def index(rtype=None, rstate="active"):
8689
8790 data_handler_module = current_app .config ["DASHBOARD" ].get (rtype ).get ("data_handler" , models )
8891 data_handler_method = current_app .config ["DASHBOARD" ].get (rtype ).get ("data_handler_method" , "get_ip_rules" )
89-
92+
9093 # Get pagination parameters
9194 page = request .args .get (PAGE_ARG , 1 , type = int )
9295 per_page = request .args .get (PER_PAGE_ARG , PER_PAGE_DEFAULT , type = int )
93-
96+
9497 # Validate per_page
9598 if per_page not in PER_PAGE_OPTIONS :
9699 per_page = PER_PAGE_DEFAULT
97-
98- # Determine if pagination should be used (only for 'expired' and 'all')
99- use_pagination = rstate in ['expired' , 'all' ]
100-
100+
101101 # get search query, sort order and sort key from request or session
102102 get_search_query = request .args .get (SEARCH_ARG , session .get (SEARCH_ARG , "" ))
103103 get_sort_key = request .args .get (SORT_ARG , session .get (SORT_ARG , DEFAULT_SORT ))
@@ -121,40 +121,56 @@ def index(rtype=None, rstate="active"):
121121
122122 # get the handler and the data
123123 handler = getattr (data_handler_module , data_handler_method )
124-
125- # Call handler with pagination if applicable
126- if use_pagination and not get_search_query :
127- # Use paginated version
128- rules_data = handler (rtype , rstate , get_sort_key , get_sort_order , page = page , per_page = per_page , paginate = True )
129- if isinstance (rules_data , tuple ):
130- rules , pagination = rules_data
131- else :
132- # Fallback if handler doesn't support pagination yet
133- rules = rules_data
134- pagination = None
135- else :
136- # Use non-paginated version (for active or when searching)
137- rules = handler (rtype , rstate , get_sort_key , get_sort_order )
138- pagination = None
139124
140- # Enrich rules with whitelist information
141- rules , whitelist_rule_ids = enrich_rules_with_whitelist_info ( rules , rtype )
125+ # Determine if we're searching
126+ is_searching = bool ( get_search_query )
142127
143- # search rules
144- if get_search_query :
145- count_match = current_app .config ["COUNT_MATCH" ]
128+ # Always fetch ALL rules first (no pagination at DB level when searching)
129+ if is_searching :
130+ # Get all rules for search
131+ rules = handler (rtype , rstate , get_sort_key , get_sort_order , paginate = False )
132+
133+ # Perform search on all rules
146134 rules = filter_rules (rules , get_search_query )
147- # extended search in for all rule types
148- count_match [rtype ] = len (rules )
135+
136+ # Now paginate the search results in memory
137+ pagination = paginate_list (rules , page , per_page )
138+ # Get the slice of rules for current page
139+ start_idx = (page - 1 ) * per_page
140+ end_idx = start_idx + per_page
141+ rules = rules [start_idx :end_idx ]
142+
143+ # Get counts for other rule types
144+ count_match = current_app .config ["COUNT_MATCH" ]
145+ count_match [rtype ] = pagination .total
149146 for other_rtype in other_rtypes (rtype ):
150- other_rules = handler (other_rtype , rstate )
147+ other_rules = handler (other_rtype , rstate , get_sort_key , get_sort_order , paginate = False )
151148 other_rules = filter_rules (other_rules , get_search_query )
152149 count_match [other_rtype ] = len (other_rules )
153- # Disable pagination when searching
154- pagination = None
155150 else :
151+ # No search - use normal pagination or fetch all
152+ use_pagination = rstate in ["expired" , "all" ]
153+
154+ if use_pagination :
155+ # Use paginated version from DB
156+ rules_data = handler (
157+ rtype , rstate , get_sort_key , get_sort_order , page = page , per_page = per_page , paginate = True
158+ )
159+ if isinstance (rules_data , tuple ):
160+ rules , pagination = rules_data
161+ else :
162+ rules = rules_data
163+ pagination = None
164+ else :
165+ # Fetch all rules for 'active' state
166+ rules = handler (rtype , rstate , get_sort_key , get_sort_order , paginate = False )
167+ pagination = None
168+
156169 count_match = ""
157170
171+ # Enrich rules with whitelist information
172+ rules , whitelist_rule_ids = enrich_rules_with_whitelist_info (rules , rtype )
173+
158174 allowed_communities = current_app .config ["ALLOWED_COMMUNITIES" ]
159175
160176 return view_factory (
@@ -180,6 +196,72 @@ def index(rtype=None, rstate="active"):
180196 )
181197
182198
199+ # Add this route to your dashboard.py Blueprint
200+
201+
202+ @dashboard .route ("/clear-search" )
203+ @auth_required
204+ def clear_search ():
205+ """
206+ Clear the search query from session and redirect back to the current view.
207+ """
208+ # Get current rtype and rstate before clearing
209+ rtype = session .get (TYPE_ARG , next (iter (current_app .config ["DASHBOARD" ].keys ())))
210+ rstate = session .get (RULE_ARG , "active" )
211+ sort_key = session .get (SORT_ARG , DEFAULT_SORT )
212+ sort_order = session .get (ORDER_ARG , DEFAULT_ORDER )
213+
214+ # Clear the search query from session
215+ session [SEARCH_ARG ] = ""
216+
217+ # Redirect back to dashboard with current settings but no search
218+ return redirect (url_for ("dashboard.index" , rtype = rtype , rstate = rstate , sort = sort_key , order = sort_order ))
219+
220+
221+ ## Helper functions
222+
223+
224+ def paginate_list (items , page , per_page ):
225+ """
226+ Create a pagination object from a list of items.
227+ This mimics SQLAlchemy's pagination for in-memory lists.
228+
229+ :param items: List of items to paginate
230+ :param page: Current page number (1-indexed)
231+ :param per_page: Number of items per page
232+ :return: Pagination-like object
233+ """
234+ total = len (items )
235+ pages = (total + per_page - 1 ) // per_page # Ceiling division
236+
237+ has_prev = page > 1
238+ has_next = page < pages
239+
240+ prev_num = page - 1 if has_prev else None
241+ next_num = page + 1 if has_next else None
242+
243+ # Calculate first and last item numbers for display
244+ first = (page - 1 ) * per_page + 1 if total > 0 else 0
245+ last = min (page * per_page , total )
246+
247+ class Pagination :
248+ pass
249+
250+ pagination = Pagination ()
251+ pagination .page = page
252+ pagination .per_page = per_page
253+ pagination .total = total
254+ pagination .pages = pages
255+ pagination .has_prev = has_prev
256+ pagination .has_next = has_next
257+ pagination .prev_num = prev_num
258+ pagination .next_num = next_num
259+ pagination .first = first
260+ pagination .last = last
261+
262+ return pagination
263+
264+
183265def create_dashboard_table_body (
184266 rules ,
185267 rtype ,
@@ -571,11 +653,25 @@ def create_view_response(
571653
572654
573655def filter_rules (rules , get_search_query ):
656+ """
657+ Filter rules based on search query.
658+ Performs full-text search across all rule fields.
659+
660+ :param rules: List of rule objects
661+ :param get_search_query: Search string
662+ :return: Filtered list of rules
663+ """
664+ if not get_search_query :
665+ return rules
666+
574667 rules_serialized = [rule .dict () for rule in rules ]
575668 result = []
669+ search_lower = get_search_query .lower ()
670+
576671 for idx , rule in enumerate (rules_serialized ):
577- full_text = " " .join ("{}" .format (c ) for c in rule .values ())
578- if get_search_query .lower () in full_text .lower ():
672+ # Create a full text string from all values
673+ full_text = " " .join (str (c ) for c in rule .values ())
674+ if search_lower in full_text .lower ():
579675 result .append (rules [idx ])
580676
581677 return result
0 commit comments