@@ -122,6 +122,7 @@ async def start(self):
122122 app .router .add_get ("/charts" , self .html_charts )
123123 app .router .add_get ("/config" , self .html_config )
124124 app .router .add_get ("/entity" , self .html_entity )
125+ app .router .add_post ("/entity" , self .html_entity_post )
125126 app .router .add_post ("/config" , self .html_config_post )
126127 app .router .add_get ("/dash" , self .html_dash )
127128 app .router .add_post ("/dash" , self .html_dash_post )
@@ -694,6 +695,10 @@ async def html_entity(self, request):
694695 else :
695696 text += config_text
696697
698+ control_html = self .get_entity_control_html (entity , days )
699+ if control_html :
700+ text += control_html
701+
697702 text += "<h2>History Chart</h2>\n "
698703 text += '<div id="chart"></div>'
699704 now_str = self .base .now_utc .strftime (TIME_FORMAT )
@@ -736,6 +741,186 @@ async def html_entity(self, request):
736741 text += "</body></html>\n "
737742 return web .Response (content_type = "text/html" , text = text )
738743
744+ async def html_entity_post (self , request ):
745+ """
746+ Handle POST request for entity value updates
747+ """
748+ try :
749+ postdata = await request .post ()
750+ entity_id = postdata .get ("entity_id" , "" )
751+ new_value = postdata .get ("value" , "" )
752+ days = postdata .get ("days" , "7" )
753+ attributes = self .base .dashboard_values .get (entity_id , {}).get ("attributes" , {})
754+
755+ if entity_id :
756+ # Determine entity type from entity_id domain
757+ domain = entity_id .split ("." )[0 ] if "." in entity_id else ""
758+
759+ # Convert value based on entity type
760+ if domain in ["switch" , "input_boolean" ]:
761+ # For toggle switches, check if value is 'on' or 'off'
762+ new_value = new_value .lower () == "on"
763+ elif domain in ["number" , "input_number" ]:
764+ try :
765+ new_value = float (new_value ) if new_value else 0.0
766+ except ValueError :
767+ new_value = 0.0
768+ elif domain in ["select" , "input_select" ]:
769+ # Keep as string for select entities
770+ pass
771+
772+ # Set the entity state
773+ await self .base .ha_interface .set_state_external (entity_id , new_value , attributes = attributes )
774+ self .log (f"Entity { entity_id } updated to { new_value } via web interface" )
775+
776+ except Exception as e :
777+ self .log (f"Error updating entity value: { e } " )
778+
779+ # Redirect back to entity page with same parameters
780+ redirect_url = f"./entity?entity_id={ entity_id } &days={ days } "
781+ raise web .HTTPFound (redirect_url )
782+
783+ def is_entity_writable (self , entity_id ):
784+ """
785+ Determine if an entity is writable and return its control type
786+ Returns: (is_writable, control_type, options)
787+ """
788+ if not entity_id or "." not in entity_id :
789+ return False , None , None
790+
791+ domain = entity_id .split ("." )[0 ]
792+
793+ # Check if it's a writable entity type
794+ if domain in ["switch" , "input_boolean" ]:
795+ return True , "toggle" , None
796+ elif domain in ["number" , "input_number" ]:
797+ return True , "number" , None
798+ elif domain in ["select" , "input_select" ]:
799+ options = self .base .get_state_wrapper (entity_id = entity_id , attribute = "options" , default = [])
800+ return True , "select" , options
801+
802+ return False , None , None
803+
804+ def get_entity_control_html (self , entity_id , days ):
805+ """
806+ Generate HTML control for writable entities
807+ """
808+ current_value = self .base .get_state_wrapper (entity_id = entity_id )
809+
810+ is_writable , control_type , options = self .is_entity_writable (entity_id )
811+
812+ if not is_writable :
813+ return ""
814+
815+ html = '<div class="entity-edit-container" style="margin-top: 15px; padding: 15px; border: 1px solid var(--border-color, #ddd); border-radius: 5px; background-color: var(--background-secondary, #f9f9f9);">'
816+
817+ # Add CSS for dark mode support
818+ html += """
819+ <style>
820+ .entity-edit-container {
821+ --border-color: #ddd;
822+ --background-secondary: #f9f9f9;
823+ --text-color: #333;
824+ --text-secondary: #666;
825+ --input-background: #fff;
826+ }
827+
828+ body.dark-mode .entity-edit-container {
829+ --border-color: #555;
830+ --background-secondary: #2d2d2d;
831+ --text-color: #e0e0e0;
832+ --text-secondary: #bbb;
833+ --input-background: #3d3d3d;
834+ color: var(--text-color);
835+ }
836+
837+ body.dark-mode .entity-edit-container input[type="number"],
838+ body.dark-mode .entity-edit-container select {
839+ background-color: var(--input-background) !important;
840+ border-color: var(--border-color) !important;
841+ color: var(--text-color) !important;
842+ }
843+
844+ body.dark-mode .entity-edit-container span {
845+ color: var(--text-color) !important;
846+ }
847+ </style>
848+ """
849+ html += f'<form method="post" action="./entity" style="display: flex; align-items: center; gap: 10px;">'
850+ html += f'<input type="hidden" name="entity_id" value="{ entity_id } ">'
851+ html += f'<input type="hidden" name="days" value="{ days } ">'
852+
853+ if control_type == "toggle" :
854+ is_active = str (current_value ).lower () in ["true" , "on" , "1" ]
855+ toggle_class = "toggle-switch active" if is_active else "toggle-switch"
856+
857+ html += f'<div style="display: flex; align-items: center; gap: 10px;">'
858+ html += f'<button class="{ toggle_class } " type="button" onclick="toggleEntitySwitch(this, \' { entity_id } \' , { days } )"></button>'
859+ html += f"<span>Enable/Disable</span>"
860+ html += f"</div>"
861+
862+ # Add JavaScript for toggle functionality
863+ html += f"""
864+ <script>
865+ function toggleEntitySwitch(button, entityId, days) {{
866+ // Toggle the visual state
867+ button.classList.toggle('active');
868+
869+ // Determine the new value
870+ const newValue = button.classList.contains('active') ? 'on' : 'off';
871+
872+ // Create form data
873+ const formData = new FormData();
874+ formData.append('entity_id', entityId);
875+ formData.append('days', days);
876+ formData.append('value', newValue);
877+
878+ // Submit the form
879+ fetch('./entity', {{
880+ method: 'POST',
881+ body: formData
882+ }}).then(response => {{
883+ if (response.redirected) {{
884+ window.location.href = response.url;
885+ }}
886+ }}).catch(error => {{
887+ console.error('Error:', error);
888+ // Revert the toggle on error
889+ button.classList.toggle('active');
890+ }});
891+ }}
892+ </script>
893+ """
894+
895+ elif control_type == "number" :
896+ min_val = self .base .get_state_wrapper (entity_id = entity_id , attribute = "min" , default = None )
897+ max_val = self .base .get_state_wrapper (entity_id = entity_id , attribute = "max" , default = None )
898+ step = self .base .get_state_wrapper (entity_id = entity_id , attribute = "step" , default = 1 )
899+ unit = self .base .get_state_wrapper (entity_id = entity_id , attribute = "unit_of_measurement" , default = "" )
900+
901+ unit_display = f" { unit } " if unit else ""
902+
903+ html += f'<input type="number" name="value" value="{ current_value } " '
904+ if min_val is not None and max_val is not None and step is not None :
905+ html += f'min="{ min_val } " max="{ max_val } " step="{ step } " '
906+ html += f'style="padding: 8px; border-radius: 4px; border: 1px solid var(--border-color, #ccc); width: 120px; background-color: var(--input-background, #fff); color: var(--text-color, #333);" '
907+ if unit_display :
908+ html += f'<span style="margin-left: 5px; color: var(--text-secondary, #666);">{ unit } </span>'
909+ html += '<button type="submit" style="padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Update</button>'
910+
911+ elif control_type == "select" and options :
912+ html += f'<select name="value" style="padding: 8px; border-radius: 4px; border: 1px solid var(--border-color, #ccc); min-width: 150px; background-color: var(--input-background, #fff); color: var(--text-color, #333);">'
913+ for option in options :
914+ selected = "selected" if str (option ) == str (current_value ) else ""
915+ html += f'<option value="{ option } " { selected } >{ option } </option>'
916+ html += "</select>"
917+ html += '<button type="submit" style="padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Update</button>'
918+
919+ html += "</form>"
920+ html += "</div>"
921+
922+ return html
923+
739924 def get_entity_detailedForecast (self , entity , subitem = "pv_estimate" ):
740925 results = {}
741926 detailedForecast = self .base .dashboard_values .get (entity , {}).get ("attributes" , {}).get ("detailedForecast" , {})
0 commit comments