55from typing import Any
66
77from core .domain .entities import DeviceStatus , DiscoveredDevice
8+ from core .domain .enums .enums import Status
89from rich .console import Console
910from rich .table import Table
1011
@@ -21,27 +22,45 @@ def __init__(self, console: Console):
2122 self ._console = console
2223
2324 def format_device_table (
24- self , devices : list [Any ], title : str = "Shelly Devices"
25+ self ,
26+ devices : list [Any ],
27+ title : str = "Shelly Devices" ,
28+ include_beta : bool = False ,
2529 ) -> None :
2630 """
2731 Format and display a table of devices.
2832
2933 Args:
3034 devices: List of device objects or dictionaries
3135 title: Table title
36+ include_beta: Whether beta-only updates should be surfaced as
37+ available, instead of reported as up to date
3238 """
3339 if not devices :
3440 return
3541
3642 if devices and isinstance (devices [0 ], DiscoveredDevice ):
37- self ._format_discovered_devices_table (devices , title )
43+ self ._format_discovered_devices_table (devices , title , include_beta )
3844 elif devices and isinstance (devices [0 ], DeviceStatus ):
39- self ._format_device_status_table (devices )
45+ self ._format_device_status_table (devices , include_beta )
4046 else :
4147 self ._format_legacy_device_table (devices , title )
4248
49+ def _effective_status (self , device : DiscoveredDevice , include_beta : bool ) -> str :
50+ """The status to display, downgrading a beta-only update to "no
51+ update needed" when beta visibility is off — mirrors the same
52+ allowed-channels rule the web UI applies."""
53+ status = device .status
54+ if (
55+ not include_beta
56+ and str (status ) == Status .UPDATE_AVAILABLE .value
57+ and getattr (device , "available_firmware_channel" , None ) == "beta"
58+ ):
59+ return Status .NO_UPDATE_NEEDED .value
60+ return status
61+
4362 def _format_discovered_devices_table (
44- self , devices : list [DiscoveredDevice ], title : str
63+ self , devices : list [DiscoveredDevice ], title : str , include_beta : bool = False
4564 ) -> None :
4665 """Format table for DiscoveredDevice entities."""
4766 table = Table (title = title )
@@ -61,17 +80,19 @@ def _format_discovered_devices_table(
6180 device .model_name or device .device_type or "Unknown" ,
6281 device .device_name or "Unknown" ,
6382 device .firmware_version or "Unknown" ,
64- format_device_status (device . status ),
83+ format_device_status (self . _effective_status ( device , include_beta ) ),
6584 response_time ,
6685 )
6786
6887 self ._console .print (table )
6988
70- def _format_device_status_table (self , devices : list [DeviceStatus ]) -> None :
89+ def _format_device_status_table (
90+ self , devices : list [DeviceStatus ], include_beta : bool = False
91+ ) -> None :
7192 """Format table for DeviceStatus entities."""
7293
7394 for device_status in devices :
74- self .format_detailed_device_status (device_status )
95+ self .format_detailed_device_status (device_status , include_beta )
7596
7697 def _format_legacy_device_table (self , devices : list [Any ], title : str ) -> None :
7798 """Legacy format for backward compatibility."""
@@ -106,7 +127,9 @@ def _format_legacy_device_table(self, devices: list[Any], title: str) -> None:
106127
107128 self ._console .print (table )
108129
109- def format_detailed_device_status (self , device_status : DeviceStatus ) -> None :
130+ def format_detailed_device_status (
131+ self , device_status : DeviceStatus , include_beta : bool = False
132+ ) -> None :
110133 """Format detailed component information for a single device."""
111134 from rich .columns import Columns
112135 from rich .panel import Panel
@@ -132,18 +155,33 @@ def format_detailed_device_status(self, device_status: DeviceStatus) -> None:
132155 # Show available firmware updates with version information
133156 if system_info .available_updates :
134157 device_summary = device_status .get_device_summary ()
135- available_updates = device_summary .get ("available_updates" , {})
158+ raw_available_updates = device_summary .get ("available_updates" , {})
159+ available_updates = (
160+ raw_available_updates
161+ if include_beta
162+ else {
163+ channel : info
164+ for channel , info in raw_available_updates .items ()
165+ if channel != "beta"
166+ }
167+ )
136168
137169 if available_updates :
138170 system_content .append ("[yellow]Updates Available:[/yellow]" )
139171 for update_type , update_info in available_updates .items ():
140172 version = update_info .get ("version" , "Unknown" )
141173 name = update_info .get ("name" , update_type ) or update_type
142174 system_content .append (f" [cyan]• { name } :[/cyan] { version } " )
143- else :
175+ elif not raw_available_updates :
176+ # The raw component data has entries but none carried a
177+ # usable version, so get_device_summary filtered
178+ # everything out — unrelated to beta visibility.
144179 system_content .append (
145180 f"[yellow]Updates Available:[/yellow] { len (system_info .available_updates )} "
146181 )
182+ # else: only a beta release exists and beta visibility is
183+ # off — show nothing, exactly like a device with no
184+ # updates at all. No partial hint.
147185
148186 system_panel = Panel (
149187 "\n " .join (system_content ),
0 commit comments