5050 get_apps_css ,
5151 get_html_config_css ,
5252 get_apps_js ,
53+ get_apps_filter_js ,
54+ get_filter_css ,
5355 get_components_css ,
5456 get_discovery_css ,
5557 get_entity_modal_css ,
@@ -2878,6 +2880,10 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
28782880 text += "<table>"
28792881 for idx , item in enumerate (value ):
28802882 nested_path = f"{ list_path } [{ idx } ]"
2883+ # An apps.yaml key can hold any character, including the quotes the attributes
2884+ # below are delimited with - escape once here so a key such as "it's" cannot
2885+ # truncate data-nested-path/data-path and hand the browser a wrong path
2886+ nested_path_attr = html_module .escape (nested_path , quote = True )
28812887
28822888 # Check if this list item is editable
28832889 can_edit = self .is_editable_value (item )
@@ -2891,9 +2897,9 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
28912897 if can_edit :
28922898 if isinstance (item , bool ):
28932899 toggle_class = "toggle-button active" if item else "toggle-button"
2894- actions_cell = f'<button class="{ toggle_class } " onclick="toggleNestedValue({ nested_row_id } )" data-value="{ str (item ).lower ()} " data-path="{ nested_path } "></button>'
2900+ actions_cell = f'<button class="{ toggle_class } " onclick="toggleNestedValue({ nested_row_id } )" data-value="{ str (item ).lower ()} " data-path="{ nested_path_attr } "></button>'
28952901 else :
2896- actions_cell = f'<button class="edit-button" onclick="editNestedValue({ nested_row_id } )" data-path="{ nested_path } ">Edit</button>'
2902+ actions_cell = f'<button class="edit-button" onclick="editNestedValue({ nested_row_id } )" data-path="{ nested_path_attr } ">Edit</button>'
28972903
28982904 # Store the nested value info for later processing
28992905 if not hasattr (self , "_nested_values" ):
@@ -2906,7 +2912,7 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
29062912 raw_value = self .resolve_value_raw (arg , item )
29072913
29082914 if nested_row_id is not None :
2909- text += f"<tr id='nested_row_{ nested_row_id } ' data-nested-path='{ nested_path } ' data-nested-original='{ html_module .escape (str (raw_value ))} '><td>- </td><td id='nested_value_{ nested_row_id } '>{ self .render_type (arg , item , nested_path , row_counter )} </td><td>{ actions_cell } </td></tr>\n "
2915+ text += f"<tr id='nested_row_{ nested_row_id } ' data-nested-path='{ nested_path_attr } ' data-nested-original='{ html_module .escape (str (raw_value ))} '><td>- </td><td id='nested_value_{ nested_row_id } '>{ self .render_type (arg , item , nested_path , row_counter )} </td><td>{ actions_cell } </td></tr>\n "
29102916 else :
29112917 text += "<tr><td>- {}</td></tr>\n " .format (self .render_type (arg , item , nested_path , row_counter ))
29122918 text += self .render_add_row ("addListItem" , [list_path , arg ], "Add item" , row_counter )
@@ -2916,6 +2922,7 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
29162922 text += "<table>"
29172923 for key in value :
29182924 nested_path = f"{ dict_path } .{ key } "
2925+ nested_path_attr = html_module .escape (nested_path , quote = True )
29192926 nested_value = value [key ]
29202927
29212928 # Check if this nested value is editable
@@ -2930,9 +2937,9 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
29302937 if can_edit :
29312938 if isinstance (nested_value , bool ):
29322939 toggle_class = "toggle-button active" if nested_value else "toggle-button"
2933- actions_cell = f'<button class="{ toggle_class } " onclick="toggleNestedValue({ nested_row_id } )" data-value="{ str (nested_value ).lower ()} " data-path="{ nested_path } "></button>'
2940+ actions_cell = f'<button class="{ toggle_class } " onclick="toggleNestedValue({ nested_row_id } )" data-value="{ str (nested_value ).lower ()} " data-path="{ nested_path_attr } "></button>'
29342941 else :
2935- actions_cell = f'<button class="edit-button" onclick="editNestedValue({ nested_row_id } )" data-path="{ nested_path } ">Edit</button>'
2942+ actions_cell = f'<button class="edit-button" onclick="editNestedValue({ nested_row_id } )" data-path="{ nested_path_attr } ">Edit</button>'
29362943
29372944 # Store the nested value info for later processing
29382945 if not hasattr (self , "_nested_values" ):
@@ -2945,7 +2952,7 @@ def render_type(self, arg, value, parent_path="", row_counter=None):
29452952 raw_value = self .resolve_value_raw (key , nested_value )
29462953
29472954 if nested_row_id is not None :
2948- text += f"<tr id='nested_row_{ nested_row_id } ' data-nested-path='{ nested_path } ' data-nested-original='{ html_module .escape (str (raw_value ))} '><td><b>{ key } : </b></td><td id='nested_value_{ nested_row_id } '>{ self .render_type (key , nested_value , nested_path , row_counter )} </td><td>{ actions_cell } </td></tr>\n "
2955+ text += f"<tr id='nested_row_{ nested_row_id } ' data-nested-path='{ nested_path_attr } ' data-nested-original='{ html_module .escape (str (raw_value ))} '><td><b>{ key } : </b></td><td id='nested_value_{ nested_row_id } '>{ self .render_type (key , nested_value , nested_path , row_counter )} </td><td>{ actions_cell } </td></tr>\n "
29492956 else :
29502957 text += "<tr><td><b>{}: </b></td><td colspan='2'>{}</td></tr>\n " .format (key , self .render_type (key , nested_value , nested_path , row_counter ))
29512958 text += self .render_add_row ("addDictKey" , [dict_path ], "Add setting" , row_counter )
@@ -3778,13 +3785,17 @@ async def html_apps(self, request):
37783785 self .log (f"Error serializing all_states for web interface: { e } " )
37793786 all_states_json = "{}"
37803787
3781- # Add CSS styles for edit functionality
3788+ # Add CSS styles for edit functionality and for the filter box
37823789 text += get_apps_css ()
3790+ text += get_filter_css ()
37833791 text += "<body>\n "
37843792
37853793 # JavaScript for edit functionality
37863794 text += get_apps_js (all_states_json )
37873795
3796+ # JavaScript for the filter box
3797+ text += get_apps_filter_js ()
3798+
37883799 # Add message container
37893800 text += '<div id="messageContainer" class="message-container"></div>\n '
37903801
@@ -3797,6 +3808,15 @@ async def html_apps(self, request):
37973808 <button id="saveAllButton" class="save-all-button" onclick="saveAllChanges()" disabled>Save All Changes</button>
37983809 <button id="discardAllButton" class="discard-all-button" onclick="discardAllChanges()" disabled>Discard Changes</button>
37993810</div>
3811+ """
3812+
3813+ # Filter box, matching the one on the Config page (issue #5210)
3814+ text += """
3815+ <div class="filter-container">
3816+ <label for="appsFilter"><strong>Filter settings:</strong></label>
3817+ <input type="text" id="appsFilter" class="filter-input" placeholder="Type to filter settings..." oninput="filterApps()" />
3818+ <button type="button" style="margin-left: 10px; padding: 8px 12px;" onclick="document.getElementById('appsFilter').value=''; filterApps();">Clear</button>
3819+ </div>
38003820"""
38013821
38023822 warning = ""
@@ -3827,14 +3847,17 @@ async def html_apps(self, request):
38273847 value = args [arg ]
38283848 raw_value = self .resolve_value_raw (arg , value )
38293849 arg_errors = self .base .arg_errors .get (arg , "" )
3850+ # The filter box and the editor both read the row back through data-arg-name, so an
3851+ # apps.yaml key holding a quote must not be able to truncate the attribute
3852+ arg_attr = html_module .escape (str (arg ), quote = True )
38303853
38313854 # Determine if this value can be edited
38323855 # Lists should not be editable at the top level - only their individual items
38333856 can_edit = self .is_editable_value (value ) and not arg_errors and not isinstance (value , list )
38343857
38353858 if arg_errors :
38363859 text += '<tr id="row_{}" data-arg-name="{}" data-original-value="{}"><td bgcolor=#FF7777><span title="{}">⚠{}</span></td><td>{}</td><td></td></tr>\n ' .format (
3837- row_id , arg , html_module .escape (str (raw_value )), arg_errors , arg , self .render_type (arg , value , "" , row_counter )
3860+ row_id , arg_attr , html_module .escape (str (raw_value )), arg_errors , arg , self .render_type (arg , value , "" , row_counter )
38383861 )
38393862 else :
38403863 actions_cell = ""
@@ -3848,7 +3871,7 @@ async def html_apps(self, request):
38483871 actions_cell = f'<button class="edit-button" onclick="editValue({ row_id } )">Edit</button>'
38493872
38503873 text += '<tr id="row_{}" data-arg-name="{}" data-original-value="{}"><td>{}</td><td id="value_{}">{}</td><td>{}</td></tr>\n ' .format (
3851- row_id , arg , html_module .escape (str (raw_value )), arg , row_id , self .render_type (arg , value , "" , row_counter ), actions_cell
3874+ row_id , arg_attr , html_module .escape (str (raw_value )), arg , row_id , self .render_type (arg , value , "" , row_counter ), actions_cell
38523875 )
38533876 row_id += 1
38543877
@@ -3857,7 +3880,7 @@ async def html_apps(self, request):
38573880 value = args [arg ]
38583881 raw_value = self .resolve_value_raw (arg , value )
38593882 text += '<tr id="row_{}" data-arg-name="{}" data-original-value="{}"><td>{}</td><td><span style="background-color:#FFAAAA">{}</span></td><td></td></tr>\n ' .format (
3860- row_id , arg , html_module .escape (str (raw_value )), arg , self .render_type (arg , value , "" , row_counter )
3883+ row_id , html_module . escape ( str ( arg ), quote = True ) , html_module .escape (str (raw_value )), arg , self .render_type (arg , value , "" , row_counter )
38613884 )
38623885 row_id += 1
38633886
0 commit comments