eeroctl 1.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eeroctl/__init__.py +19 -0
- eeroctl/commands/__init__.py +32 -0
- eeroctl/commands/activity.py +237 -0
- eeroctl/commands/auth.py +471 -0
- eeroctl/commands/completion.py +142 -0
- eeroctl/commands/device.py +492 -0
- eeroctl/commands/eero/__init__.py +12 -0
- eeroctl/commands/eero/base.py +224 -0
- eeroctl/commands/eero/led.py +154 -0
- eeroctl/commands/eero/nightlight.py +235 -0
- eeroctl/commands/eero/updates.py +82 -0
- eeroctl/commands/network/__init__.py +18 -0
- eeroctl/commands/network/advanced.py +191 -0
- eeroctl/commands/network/backup.py +162 -0
- eeroctl/commands/network/base.py +331 -0
- eeroctl/commands/network/dhcp.py +118 -0
- eeroctl/commands/network/dns.py +197 -0
- eeroctl/commands/network/forwards.py +115 -0
- eeroctl/commands/network/guest.py +162 -0
- eeroctl/commands/network/security.py +162 -0
- eeroctl/commands/network/speedtest.py +99 -0
- eeroctl/commands/network/sqm.py +194 -0
- eeroctl/commands/profile.py +671 -0
- eeroctl/commands/troubleshoot.py +317 -0
- eeroctl/context.py +254 -0
- eeroctl/errors.py +156 -0
- eeroctl/exit_codes.py +68 -0
- eeroctl/formatting/__init__.py +90 -0
- eeroctl/formatting/base.py +181 -0
- eeroctl/formatting/device.py +430 -0
- eeroctl/formatting/eero.py +591 -0
- eeroctl/formatting/misc.py +87 -0
- eeroctl/formatting/network.py +659 -0
- eeroctl/formatting/profile.py +443 -0
- eeroctl/main.py +161 -0
- eeroctl/options.py +429 -0
- eeroctl/output.py +739 -0
- eeroctl/safety.py +259 -0
- eeroctl/utils.py +181 -0
- eeroctl-1.7.1.dist-info/METADATA +115 -0
- eeroctl-1.7.1.dist-info/RECORD +45 -0
- eeroctl-1.7.1.dist-info/WHEEL +5 -0
- eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
- eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
- eeroctl-1.7.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"""Network formatting utilities for the Eero CLI.
|
|
2
|
+
|
|
3
|
+
This module provides formatting functions for displaying network data.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from eero.models.network import Network
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
DetailLevel,
|
|
14
|
+
build_panel,
|
|
15
|
+
console,
|
|
16
|
+
field,
|
|
17
|
+
field_bool,
|
|
18
|
+
field_status,
|
|
19
|
+
format_bool,
|
|
20
|
+
format_network_status,
|
|
21
|
+
get_network_status_value,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# ==================== Network Table ====================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_network_table(networks: List[Network]) -> Table:
|
|
28
|
+
"""Create a table displaying networks.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
networks: List of Network objects
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Rich Table object
|
|
35
|
+
"""
|
|
36
|
+
table = Table(title="Eero Networks")
|
|
37
|
+
table.add_column("ID", style="dim")
|
|
38
|
+
table.add_column("Name", style="cyan")
|
|
39
|
+
table.add_column("Status", style="green")
|
|
40
|
+
table.add_column("Public IP", style="blue")
|
|
41
|
+
table.add_column("ISP", style="magenta")
|
|
42
|
+
table.add_column("Created", style="yellow")
|
|
43
|
+
|
|
44
|
+
for network in networks:
|
|
45
|
+
status_value = get_network_status_value(network)
|
|
46
|
+
display_status, status_style = format_network_status(status_value)
|
|
47
|
+
table.add_row(
|
|
48
|
+
network.id,
|
|
49
|
+
network.name,
|
|
50
|
+
f"[{status_style}]{display_status}[/{status_style}]",
|
|
51
|
+
network.public_ip or "Unknown",
|
|
52
|
+
network.isp_name or "Unknown",
|
|
53
|
+
network.created_at.strftime("%Y-%m-%d") if network.created_at else "Unknown",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return table
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ==================== Network Brief View Panels ====================
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _network_basic_panel(network: Network, extensive: bool = False) -> Panel:
|
|
63
|
+
"""Build the basic network info panel."""
|
|
64
|
+
status_value = get_network_status_value(network)
|
|
65
|
+
display_status, status_style = format_network_status(status_value)
|
|
66
|
+
updated = network.updated_at.strftime("%Y-%m-%d %H:%M:%S") if network.updated_at else "Unknown"
|
|
67
|
+
created = network.created_at.strftime("%Y-%m-%d %H:%M:%S") if network.created_at else "Unknown"
|
|
68
|
+
|
|
69
|
+
lines = [
|
|
70
|
+
field("Name", network.name),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
if extensive:
|
|
74
|
+
lines.append(field("Display Name", network.display_name, "N/A"))
|
|
75
|
+
|
|
76
|
+
lines.extend(
|
|
77
|
+
[
|
|
78
|
+
field_status("Status", display_status, status_style),
|
|
79
|
+
field("Public IP", network.public_ip),
|
|
80
|
+
field("ISP", network.isp_name),
|
|
81
|
+
field("Created", created),
|
|
82
|
+
field("Updated", updated),
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if extensive:
|
|
87
|
+
lines.extend(
|
|
88
|
+
[
|
|
89
|
+
field("Owner", network.owner),
|
|
90
|
+
field("Network Type", network.network_customer_type),
|
|
91
|
+
field("Premium Status", network.premium_status),
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
if network.owner:
|
|
96
|
+
lines.append(field("Owner", network.owner))
|
|
97
|
+
if network.network_customer_type:
|
|
98
|
+
lines.append(field("Type", network.network_customer_type))
|
|
99
|
+
|
|
100
|
+
lines.append(field_bool("Guest Network", network.guest_network_enabled))
|
|
101
|
+
|
|
102
|
+
updates_info = getattr(network, "updates", {})
|
|
103
|
+
if updates_info and updates_info.get("has_update", False):
|
|
104
|
+
target_fw = updates_info.get("target_firmware", "")
|
|
105
|
+
lines.append(f"[bold]Update:[/bold] [yellow]Available ({target_fw})[/yellow]")
|
|
106
|
+
|
|
107
|
+
return build_panel(lines, f"Network: {network.name}", "blue")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _network_health_panel(network: Network) -> Optional[Panel]:
|
|
111
|
+
"""Build the network health panel for brief view."""
|
|
112
|
+
health_info = getattr(network, "health", {})
|
|
113
|
+
if not health_info:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
internet_info = health_info.get("internet", {})
|
|
117
|
+
eero_info = health_info.get("eero_network", {})
|
|
118
|
+
|
|
119
|
+
internet_status = internet_info.get("status", "unknown")
|
|
120
|
+
isp_up = internet_info.get("isp_up", False)
|
|
121
|
+
eero_status = eero_info.get("status", "unknown")
|
|
122
|
+
|
|
123
|
+
internet_style = "green" if internet_status == "connected" else "red"
|
|
124
|
+
eero_style = "green" if eero_status == "connected" else "red"
|
|
125
|
+
|
|
126
|
+
lines = [
|
|
127
|
+
f"[bold]Internet:[/bold] [{internet_style}]{internet_status}[/{internet_style}]",
|
|
128
|
+
f"[bold]ISP Up:[/bold] {format_bool(isp_up)}",
|
|
129
|
+
f"[bold]Eero Network:[/bold] [{eero_style}]{eero_status}[/{eero_style}]",
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
return build_panel(lines, "Network Health", "green")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _network_connection_panel(network: Network) -> Panel:
|
|
136
|
+
"""Build the connection info panel for brief view."""
|
|
137
|
+
lines = [
|
|
138
|
+
field("Gateway Type", network.gateway),
|
|
139
|
+
field("WAN Type", network.wan_type),
|
|
140
|
+
field("Gateway IP", network.gateway_ip),
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
backup_enabled = getattr(network, "backup_internet_enabled", False)
|
|
144
|
+
if backup_enabled:
|
|
145
|
+
lines.append("[bold]Backup Internet:[/bold] [green]Enabled[/green]")
|
|
146
|
+
|
|
147
|
+
ip_settings = getattr(network, "ip_settings", {})
|
|
148
|
+
if ip_settings and ip_settings.get("double_nat", False):
|
|
149
|
+
lines.append("[bold]Double NAT:[/bold] [yellow]Detected[/yellow]")
|
|
150
|
+
|
|
151
|
+
return build_panel(lines, "Connection", "green")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _network_dhcp_panel(network: Network) -> Optional[Panel]:
|
|
155
|
+
"""Build the DHCP configuration panel."""
|
|
156
|
+
if not network.dhcp:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
lines = [
|
|
160
|
+
field("Subnet Mask", network.dhcp.subnet_mask),
|
|
161
|
+
field("Starting Address", network.dhcp.starting_address),
|
|
162
|
+
field("Ending Address", network.dhcp.ending_address),
|
|
163
|
+
field("Lease Time", f"{network.dhcp.lease_time_seconds // 3600} hours"),
|
|
164
|
+
field("DNS Server", network.dhcp.dns_server, "Default"),
|
|
165
|
+
]
|
|
166
|
+
return build_panel(lines, "DHCP Configuration", "cyan")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _network_dns_brief_panel(network: Network) -> Optional[Panel]:
|
|
170
|
+
"""Build the DNS info panel for brief view."""
|
|
171
|
+
dns_info = getattr(network, "dns", {})
|
|
172
|
+
premium_dns = getattr(network, "premium_dns", {})
|
|
173
|
+
|
|
174
|
+
if not dns_info and not premium_dns:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
lines = []
|
|
178
|
+
|
|
179
|
+
if dns_info:
|
|
180
|
+
mode = dns_info.get("mode", "automatic")
|
|
181
|
+
lines.append(field("DNS Mode", mode))
|
|
182
|
+
|
|
183
|
+
caching = dns_info.get("caching", False)
|
|
184
|
+
lines.append(field_bool("DNS Caching", caching))
|
|
185
|
+
|
|
186
|
+
custom_ips = dns_info.get("custom", {}).get("ips", [])
|
|
187
|
+
if custom_ips:
|
|
188
|
+
lines.append(field("Custom DNS", ", ".join(custom_ips)))
|
|
189
|
+
|
|
190
|
+
if premium_dns:
|
|
191
|
+
dns_policies = premium_dns.get("dns_policies", {})
|
|
192
|
+
malware_block = dns_policies.get("block_malware", False)
|
|
193
|
+
ad_block = dns_policies.get("ad_block", False)
|
|
194
|
+
|
|
195
|
+
if malware_block:
|
|
196
|
+
lines.append("[bold]Malware Block:[/bold] [green]Enabled[/green]")
|
|
197
|
+
if ad_block:
|
|
198
|
+
lines.append("[bold]Ad Block:[/bold] [green]Enabled[/green]")
|
|
199
|
+
|
|
200
|
+
return build_panel(lines, "DNS & Security", "magenta") if lines else None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _network_settings_panel(network: Network) -> Panel:
|
|
204
|
+
"""Build the network settings panel."""
|
|
205
|
+
dns_caching = bool(network.dns and network.dns.get("caching", False)) if network.dns else False
|
|
206
|
+
|
|
207
|
+
lines = [
|
|
208
|
+
field_bool("IPv6 Upstream", network.ipv6_upstream),
|
|
209
|
+
field_bool(
|
|
210
|
+
"IPv6 Downstream", network.settings.ipv6_downstream if network.settings else False
|
|
211
|
+
),
|
|
212
|
+
field_bool("Band Steering", network.band_steering),
|
|
213
|
+
field_bool("Thread", network.thread),
|
|
214
|
+
field_bool("UPnP", network.upnp),
|
|
215
|
+
field_bool(
|
|
216
|
+
"WPA3 Transition", network.settings.wpa3_transition if network.settings else False
|
|
217
|
+
),
|
|
218
|
+
field_bool("DNS Caching", dns_caching),
|
|
219
|
+
field("Wireless Mode", network.wireless_mode),
|
|
220
|
+
field("MLO Mode", network.mlo_mode),
|
|
221
|
+
field_bool("SQM", network.sqm),
|
|
222
|
+
]
|
|
223
|
+
return build_panel(lines, "Network Settings", "yellow")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _network_speed_panel(network: Network) -> Optional[Panel]:
|
|
227
|
+
"""Build the speed test results panel."""
|
|
228
|
+
if not network.speed_test:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
down_value = network.speed_test.get("down", {}).get("value", 0)
|
|
232
|
+
up_value = network.speed_test.get("up", {}).get("value", 0)
|
|
233
|
+
latency_value = network.speed_test.get("latency", {}).get("value", 0)
|
|
234
|
+
test_date = network.speed_test.get("date", "Unknown")
|
|
235
|
+
|
|
236
|
+
down_str = f"{down_value:.1f}" if isinstance(down_value, float) else str(down_value)
|
|
237
|
+
up_str = f"{up_value:.1f}" if isinstance(up_value, float) else str(up_value)
|
|
238
|
+
|
|
239
|
+
lines = [
|
|
240
|
+
field("Download", f"{down_str} Mbps"),
|
|
241
|
+
field("Upload", f"{up_str} Mbps"),
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
if latency_value:
|
|
245
|
+
lines.append(field("Latency", f"{latency_value} ms"))
|
|
246
|
+
|
|
247
|
+
if test_date and test_date != "Unknown":
|
|
248
|
+
if "T" in str(test_date):
|
|
249
|
+
test_date = str(test_date)[:19].replace("T", " ")
|
|
250
|
+
lines.append(field("Tested", test_date))
|
|
251
|
+
|
|
252
|
+
return build_panel(lines, "Speed Test Results", "cyan")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _network_ddns_panel(network: Network) -> Optional[Panel]:
|
|
256
|
+
"""Build the DDNS panel for brief view."""
|
|
257
|
+
ddns_info = getattr(network, "ddns", {})
|
|
258
|
+
if not ddns_info or not ddns_info.get("enabled", False):
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
subdomain = ddns_info.get("subdomain", "")
|
|
262
|
+
lines = [
|
|
263
|
+
"[bold]DDNS:[/bold] [green]Enabled[/green]",
|
|
264
|
+
field("Subdomain", subdomain),
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
return build_panel(lines, "Dynamic DNS", "blue")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _network_integrations_panel(network: Network) -> Optional[Panel]:
|
|
271
|
+
"""Build the integrations panel for brief view."""
|
|
272
|
+
amazon_linked = getattr(network, "amazon_account_linked", False)
|
|
273
|
+
alexa_skill = getattr(network, "alexa_skill", False)
|
|
274
|
+
homekit_info = getattr(network, "homekit", {})
|
|
275
|
+
homekit_enabled = homekit_info.get("enabled", False) if homekit_info else False
|
|
276
|
+
|
|
277
|
+
if not (amazon_linked or alexa_skill or homekit_enabled):
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
lines = []
|
|
281
|
+
if amazon_linked:
|
|
282
|
+
lines.append("[bold]Amazon:[/bold] [green]Linked[/green]")
|
|
283
|
+
if alexa_skill:
|
|
284
|
+
lines.append("[bold]Alexa:[/bold] [green]Enabled[/green]")
|
|
285
|
+
if homekit_enabled:
|
|
286
|
+
lines.append("[bold]HomeKit:[/bold] [green]Enabled[/green]")
|
|
287
|
+
|
|
288
|
+
return build_panel(lines, "Integrations", "blue")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _network_organization_panel(network: Network) -> Optional[Panel]:
|
|
292
|
+
"""Build the organization/ISP panel for brief view."""
|
|
293
|
+
org_info = getattr(network, "organization", {})
|
|
294
|
+
if not org_info:
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
org_name = org_info.get("name")
|
|
298
|
+
org_type = org_info.get("type")
|
|
299
|
+
|
|
300
|
+
if not org_name:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
lines = [field("Organization", org_name)]
|
|
304
|
+
if org_type:
|
|
305
|
+
lines.append(field("Type", org_type))
|
|
306
|
+
|
|
307
|
+
return build_panel(lines, "ISP Organization", "blue")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _network_premium_brief_panel(network: Network) -> Optional[Panel]:
|
|
311
|
+
"""Build the premium status panel for brief view."""
|
|
312
|
+
premium_info = getattr(network, "premium_details", {})
|
|
313
|
+
premium_status = getattr(network, "premium_status", None)
|
|
314
|
+
|
|
315
|
+
if not premium_info and not premium_status:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
lines = []
|
|
319
|
+
|
|
320
|
+
if premium_status:
|
|
321
|
+
status_style = "green" if premium_status == "active" else "yellow"
|
|
322
|
+
lines.append(f"[bold]Status:[/bold] [{status_style}]{premium_status}[/{status_style}]")
|
|
323
|
+
|
|
324
|
+
if premium_info:
|
|
325
|
+
tier = premium_info.get("tier")
|
|
326
|
+
if tier:
|
|
327
|
+
lines.append(field("Tier", tier))
|
|
328
|
+
|
|
329
|
+
return build_panel(lines, "Premium", "magenta") if lines else None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _network_resources_panel(network: Network) -> Optional[Panel]:
|
|
333
|
+
"""Build the available resources panel."""
|
|
334
|
+
resources = getattr(network, "resources", {})
|
|
335
|
+
if not resources:
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
lines = ["[bold]Available Resources:[/bold]"] + [
|
|
339
|
+
f" • {key}: {value}" for key, value in resources.items()
|
|
340
|
+
]
|
|
341
|
+
return build_panel(lines, "Available Resources", "cyan")
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ==================== Network Extensive View Panels ====================
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _network_connection_extensive_panel(network: Network) -> Panel:
|
|
348
|
+
"""Build the connection info panel for extensive view."""
|
|
349
|
+
lines = [
|
|
350
|
+
field("Gateway Type", network.gateway),
|
|
351
|
+
field("WAN Type", network.wan_type),
|
|
352
|
+
field("Gateway IP", network.gateway_ip),
|
|
353
|
+
field("Connection Mode", network.connection_mode),
|
|
354
|
+
field("Auto Setup Mode", network.auto_setup_mode),
|
|
355
|
+
field_bool("Backup Internet", network.backup_internet_enabled),
|
|
356
|
+
field_bool("Power Saving", network.power_saving),
|
|
357
|
+
]
|
|
358
|
+
return build_panel(lines, "Connection Information", "green")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _network_geo_panel(network: Network) -> Optional[Panel]:
|
|
362
|
+
"""Build the geographic info panel for extensive view."""
|
|
363
|
+
geo_info = getattr(network, "geo_ip", {})
|
|
364
|
+
if not geo_info:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
lines = [
|
|
368
|
+
field(
|
|
369
|
+
"Country",
|
|
370
|
+
f"{geo_info.get('countryName', 'Unknown')} ({geo_info.get('countryCode', 'Unknown')})",
|
|
371
|
+
),
|
|
372
|
+
field("City", geo_info.get("city")),
|
|
373
|
+
field("Region", geo_info.get("regionName")),
|
|
374
|
+
field("Postal Code", geo_info.get("postalCode")),
|
|
375
|
+
field("Timezone", geo_info.get("timezone")),
|
|
376
|
+
field("ASN", geo_info.get("asn")),
|
|
377
|
+
]
|
|
378
|
+
return build_panel(lines, "Geographic Information", "yellow")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _network_dns_extensive_panel(network: Network) -> Optional[Panel]:
|
|
382
|
+
"""Build the DNS configuration panel for extensive view."""
|
|
383
|
+
dns_info = getattr(network, "dns", {})
|
|
384
|
+
if not dns_info:
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
parent_ips = ", ".join(dns_info.get("parent", {}).get("ips", []))
|
|
388
|
+
custom_ips = ", ".join(dns_info.get("custom", {}).get("ips", []))
|
|
389
|
+
|
|
390
|
+
lines = [
|
|
391
|
+
field("DNS Mode", dns_info.get("mode")),
|
|
392
|
+
field_bool("DNS Caching", dns_info.get("caching", False)),
|
|
393
|
+
field("Parent DNS", parent_ips or "None"),
|
|
394
|
+
field("Custom DNS", custom_ips or "None"),
|
|
395
|
+
]
|
|
396
|
+
return build_panel(lines, "DNS Configuration", "cyan")
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _network_guest_panel(network: Network) -> Panel:
|
|
400
|
+
"""Build the guest network panel for extensive view."""
|
|
401
|
+
guest_password = "[dim]********[/dim]" if network.guest_network_password else "N/A"
|
|
402
|
+
lines = [
|
|
403
|
+
field_bool("Guest Network", network.guest_network_enabled),
|
|
404
|
+
field("Guest Network Name", network.guest_network_name, "N/A"),
|
|
405
|
+
field("Guest Network Password", guest_password),
|
|
406
|
+
]
|
|
407
|
+
return build_panel(lines, "Guest Network", "magenta")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _network_health_extensive_panel(network: Network) -> Optional[Panel]:
|
|
411
|
+
"""Build the network health panel for extensive view."""
|
|
412
|
+
health_info = getattr(network, "health", {})
|
|
413
|
+
if not health_info:
|
|
414
|
+
return None
|
|
415
|
+
|
|
416
|
+
lines = [
|
|
417
|
+
field("Internet Status", health_info.get("internet", {}).get("status")),
|
|
418
|
+
field_bool("ISP Up", health_info.get("internet", {}).get("isp_up", False)),
|
|
419
|
+
field("Eero Network Status", health_info.get("eero_network", {}).get("status")),
|
|
420
|
+
]
|
|
421
|
+
return build_panel(lines, "Network Health", "green")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _network_organization_extensive_panel(network: Network) -> Optional[Panel]:
|
|
425
|
+
"""Build the organization panel for extensive view."""
|
|
426
|
+
org_info = getattr(network, "organization", {})
|
|
427
|
+
if not org_info:
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
lines = [
|
|
431
|
+
field("Organization ID", org_info.get("id")),
|
|
432
|
+
field("Organization Name", org_info.get("name")),
|
|
433
|
+
field("Organization Brand", org_info.get("brand")),
|
|
434
|
+
field("Organization Type", org_info.get("type")),
|
|
435
|
+
]
|
|
436
|
+
return build_panel(lines, "Organization", "blue")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _network_premium_panel(network: Network) -> Optional[Panel]:
|
|
440
|
+
"""Build the premium details panel for extensive view."""
|
|
441
|
+
premium_info = getattr(network, "premium_details", {})
|
|
442
|
+
if not premium_info:
|
|
443
|
+
return None
|
|
444
|
+
|
|
445
|
+
lines = [
|
|
446
|
+
field("Tier", premium_info.get("tier")),
|
|
447
|
+
field("Payment Method", premium_info.get("payment_method")),
|
|
448
|
+
field("Interval", premium_info.get("interval")),
|
|
449
|
+
field("Next Billing", premium_info.get("next_billing_event_date")),
|
|
450
|
+
field_bool("My Subscription", premium_info.get("is_my_subscription", False)),
|
|
451
|
+
field_bool("Has Payment Info", premium_info.get("has_payment_info", False)),
|
|
452
|
+
]
|
|
453
|
+
return build_panel(lines, "Premium Details", "magenta")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _network_updates_panel(network: Network) -> Optional[Panel]:
|
|
457
|
+
"""Build the updates panel for extensive view."""
|
|
458
|
+
updates_info = getattr(network, "updates", {})
|
|
459
|
+
if not updates_info:
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
lines = [
|
|
463
|
+
field_bool("Update Required", updates_info.get("update_required", False)),
|
|
464
|
+
field_bool("Can Update Now", updates_info.get("can_update_now", False)),
|
|
465
|
+
field_bool("Has Update", updates_info.get("has_update", False)),
|
|
466
|
+
field("Target Firmware", updates_info.get("target_firmware")),
|
|
467
|
+
field("Preferred Update Hour", updates_info.get("preferred_update_hour")),
|
|
468
|
+
]
|
|
469
|
+
return build_panel(lines, "Updates", "yellow")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _network_ddns_extensive_panel(network: Network) -> Optional[Panel]:
|
|
473
|
+
"""Build the DDNS panel for extensive view."""
|
|
474
|
+
ddns_info = getattr(network, "ddns", {})
|
|
475
|
+
if not ddns_info:
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
lines = [
|
|
479
|
+
field_bool("DDNS Enabled", ddns_info.get("enabled", False)),
|
|
480
|
+
field("Subdomain", ddns_info.get("subdomain")),
|
|
481
|
+
]
|
|
482
|
+
return build_panel(lines, "DDNS", "cyan")
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _network_homekit_panel(network: Network) -> Optional[Panel]:
|
|
486
|
+
"""Build the HomeKit panel for extensive view."""
|
|
487
|
+
homekit_info = getattr(network, "homekit", {})
|
|
488
|
+
if not homekit_info:
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
lines = [
|
|
492
|
+
field_bool("HomeKit Enabled", homekit_info.get("enabled", False)),
|
|
493
|
+
field_bool("Managed Network", homekit_info.get("managedNetworkEnabled", False)),
|
|
494
|
+
]
|
|
495
|
+
return build_panel(lines, "HomeKit", "green")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _network_amazon_panel(network: Network) -> Panel:
|
|
499
|
+
"""Build the Amazon integration panel for extensive view."""
|
|
500
|
+
lines = [
|
|
501
|
+
field_bool("Amazon Account Linked", getattr(network, "amazon_account_linked", False)),
|
|
502
|
+
field_bool("FFS", getattr(network, "ffs", False)),
|
|
503
|
+
field_bool("Alexa Skill", getattr(network, "alexa_skill", False)),
|
|
504
|
+
]
|
|
505
|
+
return build_panel(lines, "Amazon Integration", "blue")
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _network_ip_settings_panel(network: Network) -> Optional[Panel]:
|
|
509
|
+
"""Build the IP settings panel for extensive view."""
|
|
510
|
+
ip_settings = getattr(network, "ip_settings", {})
|
|
511
|
+
if not ip_settings:
|
|
512
|
+
return None
|
|
513
|
+
|
|
514
|
+
lines = [
|
|
515
|
+
field_bool("Double NAT", ip_settings.get("double_nat", False)),
|
|
516
|
+
field("Public IP", ip_settings.get("public_ip")),
|
|
517
|
+
]
|
|
518
|
+
return build_panel(lines, "IP Settings", "yellow")
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _network_premium_dns_panel(network: Network) -> Optional[Panel]:
|
|
522
|
+
"""Build the premium DNS panel for extensive view."""
|
|
523
|
+
premium_dns = getattr(network, "premium_dns", {})
|
|
524
|
+
if not premium_dns:
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
dns_policies = premium_dns.get("dns_policies", {})
|
|
528
|
+
lines = [
|
|
529
|
+
field_bool("DNS Policies Enabled", premium_dns.get("dns_policies_enabled", False)),
|
|
530
|
+
field("DNS Provider", premium_dns.get("dns_provider")),
|
|
531
|
+
field_bool("Block Malware", dns_policies.get("block_malware", False)),
|
|
532
|
+
field_bool("Ad Block", dns_policies.get("ad_block", False)),
|
|
533
|
+
]
|
|
534
|
+
return build_panel(lines, "Premium DNS", "magenta")
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _network_last_reboot_panel(network: Network) -> Optional[Panel]:
|
|
538
|
+
"""Build the last reboot panel for extensive view."""
|
|
539
|
+
last_reboot = getattr(network, "last_reboot", None)
|
|
540
|
+
if not last_reboot:
|
|
541
|
+
return None
|
|
542
|
+
|
|
543
|
+
return build_panel([field("Last Reboot", last_reboot)], "Last Reboot", "red")
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# ==================== Main Network Details Function ====================
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def print_network_details(network: Network, detail_level: DetailLevel = "brief") -> None:
|
|
550
|
+
"""Print network information with configurable detail level.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
network: Network object
|
|
554
|
+
detail_level: "brief" or "full"
|
|
555
|
+
"""
|
|
556
|
+
extensive = detail_level == "full"
|
|
557
|
+
|
|
558
|
+
# Basic info panel (always shown)
|
|
559
|
+
console.print(_network_basic_panel(network, extensive))
|
|
560
|
+
|
|
561
|
+
if not extensive:
|
|
562
|
+
# Brief view panels
|
|
563
|
+
|
|
564
|
+
# Health status
|
|
565
|
+
health_panel = _network_health_panel(network)
|
|
566
|
+
if health_panel:
|
|
567
|
+
console.print(health_panel)
|
|
568
|
+
|
|
569
|
+
# Connection info
|
|
570
|
+
console.print(_network_connection_panel(network))
|
|
571
|
+
|
|
572
|
+
# DHCP info
|
|
573
|
+
dhcp_panel = _network_dhcp_panel(network)
|
|
574
|
+
if dhcp_panel:
|
|
575
|
+
console.print(dhcp_panel)
|
|
576
|
+
|
|
577
|
+
# DNS & Security
|
|
578
|
+
dns_panel = _network_dns_brief_panel(network)
|
|
579
|
+
if dns_panel:
|
|
580
|
+
console.print(dns_panel)
|
|
581
|
+
|
|
582
|
+
# Network settings
|
|
583
|
+
console.print(_network_settings_panel(network))
|
|
584
|
+
|
|
585
|
+
# Speed test
|
|
586
|
+
speed_panel = _network_speed_panel(network)
|
|
587
|
+
if speed_panel:
|
|
588
|
+
console.print(speed_panel)
|
|
589
|
+
|
|
590
|
+
# Optional brief panels
|
|
591
|
+
for panel_func in [
|
|
592
|
+
_network_ddns_panel,
|
|
593
|
+
_network_integrations_panel,
|
|
594
|
+
_network_organization_panel,
|
|
595
|
+
_network_premium_brief_panel,
|
|
596
|
+
]:
|
|
597
|
+
panel = panel_func(network)
|
|
598
|
+
if panel:
|
|
599
|
+
console.print(panel)
|
|
600
|
+
|
|
601
|
+
else:
|
|
602
|
+
# Extensive view panels
|
|
603
|
+
console.print(_network_connection_extensive_panel(network))
|
|
604
|
+
|
|
605
|
+
# Optional extensive panels (first group)
|
|
606
|
+
for panel_func in [
|
|
607
|
+
_network_geo_panel,
|
|
608
|
+
]:
|
|
609
|
+
panel = panel_func(network)
|
|
610
|
+
if panel:
|
|
611
|
+
console.print(panel)
|
|
612
|
+
|
|
613
|
+
# DHCP
|
|
614
|
+
dhcp_panel = _network_dhcp_panel(network)
|
|
615
|
+
if dhcp_panel:
|
|
616
|
+
console.print(dhcp_panel)
|
|
617
|
+
|
|
618
|
+
# DNS config
|
|
619
|
+
dns_panel = _network_dns_extensive_panel(network)
|
|
620
|
+
if dns_panel:
|
|
621
|
+
console.print(dns_panel)
|
|
622
|
+
|
|
623
|
+
# Settings
|
|
624
|
+
console.print(_network_settings_panel(network))
|
|
625
|
+
|
|
626
|
+
# Guest network
|
|
627
|
+
console.print(_network_guest_panel(network))
|
|
628
|
+
|
|
629
|
+
# Speed test
|
|
630
|
+
speed_panel = _network_speed_panel(network)
|
|
631
|
+
if speed_panel:
|
|
632
|
+
console.print(speed_panel)
|
|
633
|
+
|
|
634
|
+
# Optional extensive panels
|
|
635
|
+
for panel_func in [
|
|
636
|
+
_network_health_extensive_panel,
|
|
637
|
+
_network_organization_extensive_panel,
|
|
638
|
+
_network_premium_panel,
|
|
639
|
+
_network_updates_panel,
|
|
640
|
+
_network_ddns_extensive_panel,
|
|
641
|
+
_network_homekit_panel,
|
|
642
|
+
]:
|
|
643
|
+
panel = panel_func(network)
|
|
644
|
+
if panel:
|
|
645
|
+
console.print(panel)
|
|
646
|
+
|
|
647
|
+
# Amazon integration (always shown in extensive)
|
|
648
|
+
console.print(_network_amazon_panel(network))
|
|
649
|
+
|
|
650
|
+
# Optional extensive panels (final group)
|
|
651
|
+
for panel_func in [
|
|
652
|
+
_network_ip_settings_panel,
|
|
653
|
+
_network_premium_dns_panel,
|
|
654
|
+
_network_last_reboot_panel,
|
|
655
|
+
_network_resources_panel,
|
|
656
|
+
]:
|
|
657
|
+
panel = panel_func(network)
|
|
658
|
+
if panel:
|
|
659
|
+
console.print(panel)
|