|
31 | 31 | POLLING_OPTIONS_LIST, |
32 | 32 | ) |
33 | 33 | from .helpers import is_primary_entry |
| 34 | +from .support import ( |
| 35 | + async_toggle_debug_logging, |
| 36 | + async_collect_integration_logs, |
| 37 | + async_create_downloadable_support_file, |
| 38 | +) |
34 | 39 |
|
35 | 40 | DATA_SCHEMA = vol.Schema( |
36 | 41 | { |
@@ -157,6 +162,7 @@ class DanthermOptionsFlowHandler(config_entries.OptionsFlow): |
157 | 162 |
|
158 | 163 | def __init__(self, config_entry: config_entries.ConfigEntry) -> None: |
159 | 164 | """Initialize options flow.""" |
| 165 | + self._last_download: dict[str, str] | None = None |
160 | 166 |
|
161 | 167 | def _get_polling_speed_from_interval(self, interval: int) -> str: |
162 | 168 | """Convert scan interval to polling speed option.""" |
@@ -190,29 +196,18 @@ def _get_polling_options_with_custom(self) -> list[str]: |
190 | 196 | async def async_step_init( |
191 | 197 | self, user_input: dict[str, Any] | None = None |
192 | 198 | ) -> ConfigFlowResult: |
193 | | - """Show the initial welcome screen with configuration overview.""" |
194 | | - if user_input is not None: |
195 | | - # Check if user wants to continue |
196 | | - if not user_input.get("continue", False): |
197 | | - # User unchecked continue, abort the options flow |
198 | | - return self.async_abort(reason="aborted_by_user") |
199 | | - |
200 | | - # User wants to continue, proceed to network configuration |
201 | | - return await self.async_step_network() |
202 | | - |
203 | | - # Create a schema with a read-only information field |
204 | | - schema = vol.Schema( |
205 | | - { |
206 | | - vol.Optional("continue", default=True): bool, |
207 | | - } |
208 | | - ) |
209 | | - |
210 | | - return self.async_show_form( |
| 199 | + """Show the main menu with options.""" |
| 200 | + return self.async_show_menu( |
211 | 201 | step_id="init", |
212 | | - data_schema=schema, |
213 | | - description_placeholders={}, |
| 202 | + menu_options=["continue_setup", "support"], |
214 | 203 | ) |
215 | 204 |
|
| 205 | + async def async_step_continue_setup( |
| 206 | + self, user_input: dict[str, Any] | None = None |
| 207 | + ) -> ConfigFlowResult: |
| 208 | + """Continue with regular setup.""" |
| 209 | + return await self.async_step_network() |
| 210 | + |
216 | 211 | async def async_step_network( |
217 | 212 | self, user_input: dict[str, Any] | None = None |
218 | 213 | ) -> ConfigFlowResult: |
@@ -372,3 +367,223 @@ async def async_step_advanced( |
372 | 367 | return self.async_create_entry(title="", data={}) |
373 | 368 |
|
374 | 369 | return self.async_show_form(step_id="advanced", data_schema=schema) |
| 370 | + |
| 371 | + async def async_step_support( |
| 372 | + self, user_input: dict[str, Any] | None = None |
| 373 | + ) -> ConfigFlowResult: |
| 374 | + """Show support options menu.""" |
| 375 | + if user_input is not None: |
| 376 | + # Handle debug logging toggle |
| 377 | + logger = logging.getLogger(f"custom_components.{DOMAIN}") |
| 378 | + current_debug = logger.isEnabledFor(logging.DEBUG) |
| 379 | + new_debug_setting = user_input.get("debug_logging", current_debug) |
| 380 | + |
| 381 | + # Toggle debug logging if setting changed |
| 382 | + if new_debug_setting != current_debug: |
| 383 | + await async_toggle_debug_logging(self.hass, new_debug_setting) |
| 384 | + |
| 385 | + # Handle other actions |
| 386 | + action = user_input.get("action") |
| 387 | + if action == "collect_logs": |
| 388 | + # Generate files using shared helper |
| 389 | + try: |
| 390 | + result = await async_create_downloadable_support_file( |
| 391 | + self.hass, |
| 392 | + self.config_entry.entry_id, |
| 393 | + self.config_entry.title, |
| 394 | + prefix="dantherm_support", |
| 395 | + ) |
| 396 | + |
| 397 | + self._last_download = { |
| 398 | + "filename": result["filename"], |
| 399 | + "download_url": result["forced_download_url"], |
| 400 | + "file_path": result["file_path"], |
| 401 | + "timestamp": result["timestamp"], |
| 402 | + } |
| 403 | + |
| 404 | + # Ask frontend to open the download URL in a new tab/window |
| 405 | + # while keeping the flow alive; user can then click "Done" to continue |
| 406 | + return self.async_external_step( |
| 407 | + step_id="support_download", |
| 408 | + url=result["forced_download_url"], |
| 409 | + ) |
| 410 | + |
| 411 | + except (OSError, ValueError) as ex: |
| 412 | + return self.async_create_entry( |
| 413 | + title="Fejl ved fil generering", |
| 414 | + data={"error": f"Kunne ikke generere support filer: {ex}"}, |
| 415 | + ) |
| 416 | + if action == "diagnostics_info": |
| 417 | + return await self.async_step_diagnostics_info() |
| 418 | + if action == "troubleshooting": |
| 419 | + return await self.async_step_troubleshooting() |
| 420 | + if action == "back_to_main": |
| 421 | + return await self.async_step_network() |
| 422 | + |
| 423 | + # If no specific action, stay on support page |
| 424 | + return await self.async_step_support() |
| 425 | + |
| 426 | + # Check current debug status |
| 427 | + logger = logging.getLogger(f"custom_components.{DOMAIN}") |
| 428 | + debug_enabled = logger.isEnabledFor(logging.DEBUG) |
| 429 | + |
| 430 | + schema = vol.Schema( |
| 431 | + { |
| 432 | + vol.Optional("debug_logging", default=debug_enabled): bool, |
| 433 | + vol.Optional("action"): vol.In( |
| 434 | + { |
| 435 | + "collect_logs": "Collect integration logs", |
| 436 | + "diagnostics_info": "Generate diagnostics data", |
| 437 | + "troubleshooting": "Troubleshooting guide", |
| 438 | + "back_to_main": "Back to main configuration", |
| 439 | + } |
| 440 | + ), |
| 441 | + } |
| 442 | + ) |
| 443 | + |
| 444 | + return self.async_show_form( |
| 445 | + step_id="support", |
| 446 | + data_schema=schema, |
| 447 | + description_placeholders={ |
| 448 | + "device_name": self.config_entry.title, |
| 449 | + "debug_status": "enabled" if debug_enabled else "disabled", |
| 450 | + # If a file was just generated, provide a link placeholder |
| 451 | + "download_link": ( |
| 452 | + f"[Klik her for at downloade]({self._last_download['download_url']})" |
| 453 | + if self._last_download |
| 454 | + else "" |
| 455 | + ), |
| 456 | + }, |
| 457 | + ) |
| 458 | + |
| 459 | + async def async_step_support_download_ready( |
| 460 | + self, user_input: dict[str, Any] | None = None |
| 461 | + ) -> ConfigFlowResult: |
| 462 | + """Show a small form with a clickable download link and a back button.""" |
| 463 | + if not self._last_download: |
| 464 | + return await self.async_step_support() |
| 465 | + |
| 466 | + if user_input is not None: |
| 467 | + if user_input.get("back", False): |
| 468 | + return await self.async_step_support() |
| 469 | + |
| 470 | + schema = vol.Schema({vol.Optional("back", default=False): bool}) |
| 471 | + |
| 472 | + return self.async_show_form( |
| 473 | + step_id="support_download_ready", |
| 474 | + data_schema=schema, |
| 475 | + description_placeholders={ |
| 476 | + "filename": self._last_download["filename"], |
| 477 | + "download_url": self._last_download["download_url"], |
| 478 | + "download_link": f"[Klik her for at downloade]({self._last_download['download_url']})", |
| 479 | + }, |
| 480 | + ) |
| 481 | + |
| 482 | + async def async_step_support_download( |
| 483 | + self, user_input: dict[str, Any] | None = None |
| 484 | + ) -> ConfigFlowResult: |
| 485 | + """External step 'done' handler: return to the download-ready view.""" |
| 486 | + # Once the external URL was opened, guide user to the view that also shows the link |
| 487 | + return self.async_external_step_done(next_step_id="support_download_ready") |
| 488 | + |
| 489 | + async def async_step_collect_logs( |
| 490 | + self, user_input: dict[str, Any] | None = None |
| 491 | + ) -> ConfigFlowResult: |
| 492 | + """Collect integration logs and generate download file.""" |
| 493 | + if user_input is not None: |
| 494 | + if user_input.get("back", False): |
| 495 | + return await self.async_step_support() |
| 496 | + if user_input.get("generate", False): |
| 497 | + # Generate and trigger the log file download using shared helper |
| 498 | + try: |
| 499 | + result = await async_create_downloadable_support_file( |
| 500 | + self.hass, |
| 501 | + self.config_entry.entry_id, |
| 502 | + self.config_entry.title, |
| 503 | + prefix="dantherm_logs", |
| 504 | + ) |
| 505 | + |
| 506 | + self._last_download = { |
| 507 | + "filename": result["filename"], |
| 508 | + "download_url": result["forced_download_url"], |
| 509 | + "file_path": result["file_path"], |
| 510 | + "timestamp": result["timestamp"], |
| 511 | + } |
| 512 | + |
| 513 | + # Open the download in a new tab and then show the ready step |
| 514 | + return self.async_external_step( |
| 515 | + step_id="support_download", |
| 516 | + url=result["forced_download_url"], |
| 517 | + ) |
| 518 | + |
| 519 | + except (OSError, ValueError) as ex: |
| 520 | + return self.async_show_form( |
| 521 | + step_id="collect_logs", |
| 522 | + data_schema=vol.Schema( |
| 523 | + {vol.Optional("back", default=False): bool} |
| 524 | + ), |
| 525 | + errors={"base": "log_generation_failed"}, |
| 526 | + description_placeholders={"error_message": str(ex)}, |
| 527 | + ) |
| 528 | + |
| 529 | + # Show form to generate logs |
| 530 | + try: |
| 531 | + # Quick preview of available logs |
| 532 | + logs_preview = await async_collect_integration_logs(self.hass) |
| 533 | + collection_info = logs_preview.get("collection_info", {}) |
| 534 | + |
| 535 | + status_parts = [ |
| 536 | + f"Debug logging: {'aktiveret' if collection_info.get('debug_enabled', False) else 'deaktiveret'}", |
| 537 | + f"Log entries tilgængelige: {logs_preview.get('total_entries', 0)}", |
| 538 | + "Følsomme data bliver automatisk fjernet", |
| 539 | + ] |
| 540 | + |
| 541 | + status = " • ".join(status_parts) |
| 542 | + |
| 543 | + except (AttributeError, ValueError, RuntimeError): |
| 544 | + status = "Klar til at generere log fil" |
| 545 | + |
| 546 | + schema = vol.Schema( |
| 547 | + { |
| 548 | + vol.Optional("generate", default=False): bool, |
| 549 | + vol.Optional("back", default=False): bool, |
| 550 | + } |
| 551 | + ) |
| 552 | + |
| 553 | + return self.async_show_form( |
| 554 | + step_id="collect_logs", |
| 555 | + data_schema=schema, |
| 556 | + description_placeholders={"log_status": status}, |
| 557 | + ) |
| 558 | + |
| 559 | + async def async_step_diagnostics_info( |
| 560 | + self, user_input: dict[str, Any] | None = None |
| 561 | + ) -> ConfigFlowResult: |
| 562 | + """Show diagnostics information.""" |
| 563 | + if user_input is not None: |
| 564 | + if user_input.get("back", False): |
| 565 | + return await self.async_step_support() |
| 566 | + |
| 567 | + schema = vol.Schema({vol.Optional("back", default=False): bool}) |
| 568 | + |
| 569 | + return self.async_show_form( |
| 570 | + step_id="diagnostics_info", |
| 571 | + data_schema=schema, |
| 572 | + description_placeholders={"device_name": self.config_entry.title}, |
| 573 | + ) |
| 574 | + |
| 575 | + async def async_step_troubleshooting( |
| 576 | + self, user_input: dict[str, Any] | None = None |
| 577 | + ) -> ConfigFlowResult: |
| 578 | + """Show troubleshooting guide.""" |
| 579 | + if user_input is not None: |
| 580 | + if user_input.get("back", False): |
| 581 | + return await self.async_step_support() |
| 582 | + |
| 583 | + schema = vol.Schema({vol.Optional("back", default=False): bool}) |
| 584 | + |
| 585 | + return self.async_show_form( |
| 586 | + step_id="troubleshooting", |
| 587 | + data_schema=schema, |
| 588 | + description_placeholders={"device_name": self.config_entry.title}, |
| 589 | + ) |
0 commit comments