1313from collections .abc import Sequence
1414
1515from cmk .livestatus_client import MultiSiteConnection
16+ from cmk .livestatus_client .expressions import NothingExpression , Or , QueryExpression
1617from cmk .livestatus_client .queries import detailed_connection , Query
1718from cmk .livestatus_client .tables import Hosts , Status
1819
1920from ._models import Host , HostState , ServiceCounts
2021
22+ _SEARCHABLE_COLUMNS = (Hosts .name , Hosts .alias , Hosts .address )
23+
24+
25+ def _search_filter (search_query : str ) -> QueryExpression :
26+ """Build an OR-combined case-insensitive "contains" filter over the searchable columns.
27+
28+ ``search_query`` is expected to be already whitespace-stripped. An empty value yields a no-op
29+ filter, so the resulting query is identical to one without a search.
30+ """
31+ if not search_query :
32+ return NothingExpression ()
33+ return Or (* (column .contains (search_query , ignore_case = True ) for column in _SEARCHABLE_COLUMNS ))
34+
2135
2236class LiveStatusHostRepository :
2337 def __init__ (self , * , connection : MultiSiteConnection ) -> None :
2438 self ._connection = connection
2539
26- def fetch (self , * , limit : int ) -> Sequence [Host ]:
40+ def fetch (self , * , limit : int , search_query : str = "" ) -> Sequence [Host ]:
2741 q = Query (
2842 [
2943 Hosts .name ,
@@ -37,6 +51,7 @@ def fetch(self, *, limit: int) -> Sequence[Host]:
3751 Hosts .num_services_unknown ,
3852 Hosts .num_services_pending ,
3953 ],
54+ _search_filter (search_query ),
4055 extra_headers = [
4156 f"Limit: { limit } " ,
4257 ],
@@ -62,8 +77,17 @@ def fetch(self, *, limit: int) -> Sequence[Host]:
6277 for row in q .iterate (conn )
6378 ]
6479
65- def count (self ) -> int :
66- q = Query ([Status .num_hosts ])
80+ def count (self , * , search_query : str = "" ) -> int :
81+ if not search_query :
82+ q = Query ([Status .num_hosts ])
83+ with detailed_connection (self ._connection ) as conn :
84+ return sum (row ["num_hosts" ] for row in q .iterate (conn ))
6785
68- with detailed_connection (self ._connection ) as conn :
69- return sum (row ["num_hosts" ] for row in q .iterate (conn ))
86+ # A filtered total can't be read from the ``status`` table. Count the matches server-side
87+ # via ``Stats`` instead of transferring and counting every matching row. The ``Query`` class
88+ # can't emit ``Stats`` headers yet, so the query is assembled by hand from the shared filter.
89+ # The ``Stats`` count is the trailing column of each returned row; summing it across rows
90+ # adds up the per-site counts.
91+ filter_lines = (": " .join (line ) for line in _search_filter (search_query ).render ())
92+ stats_query = "\n " .join ([f"GET { Hosts .__tablename__ } " , "Stats: state >= 0" , * filter_lines ])
93+ return sum (int (row [- 1 ]) for row in self ._connection .query (stats_query ))
0 commit comments