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.
Files changed (45) hide show
  1. eeroctl/__init__.py +19 -0
  2. eeroctl/commands/__init__.py +32 -0
  3. eeroctl/commands/activity.py +237 -0
  4. eeroctl/commands/auth.py +471 -0
  5. eeroctl/commands/completion.py +142 -0
  6. eeroctl/commands/device.py +492 -0
  7. eeroctl/commands/eero/__init__.py +12 -0
  8. eeroctl/commands/eero/base.py +224 -0
  9. eeroctl/commands/eero/led.py +154 -0
  10. eeroctl/commands/eero/nightlight.py +235 -0
  11. eeroctl/commands/eero/updates.py +82 -0
  12. eeroctl/commands/network/__init__.py +18 -0
  13. eeroctl/commands/network/advanced.py +191 -0
  14. eeroctl/commands/network/backup.py +162 -0
  15. eeroctl/commands/network/base.py +331 -0
  16. eeroctl/commands/network/dhcp.py +118 -0
  17. eeroctl/commands/network/dns.py +197 -0
  18. eeroctl/commands/network/forwards.py +115 -0
  19. eeroctl/commands/network/guest.py +162 -0
  20. eeroctl/commands/network/security.py +162 -0
  21. eeroctl/commands/network/speedtest.py +99 -0
  22. eeroctl/commands/network/sqm.py +194 -0
  23. eeroctl/commands/profile.py +671 -0
  24. eeroctl/commands/troubleshoot.py +317 -0
  25. eeroctl/context.py +254 -0
  26. eeroctl/errors.py +156 -0
  27. eeroctl/exit_codes.py +68 -0
  28. eeroctl/formatting/__init__.py +90 -0
  29. eeroctl/formatting/base.py +181 -0
  30. eeroctl/formatting/device.py +430 -0
  31. eeroctl/formatting/eero.py +591 -0
  32. eeroctl/formatting/misc.py +87 -0
  33. eeroctl/formatting/network.py +659 -0
  34. eeroctl/formatting/profile.py +443 -0
  35. eeroctl/main.py +161 -0
  36. eeroctl/options.py +429 -0
  37. eeroctl/output.py +739 -0
  38. eeroctl/safety.py +259 -0
  39. eeroctl/utils.py +181 -0
  40. eeroctl-1.7.1.dist-info/METADATA +115 -0
  41. eeroctl-1.7.1.dist-info/RECORD +45 -0
  42. eeroctl-1.7.1.dist-info/WHEEL +5 -0
  43. eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
  44. eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
  45. 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)