zebra-day 0.0.32__py3-none-any.whl → 1.0.2__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 (190) hide show
  1. zebra_day/__init__.py +35 -0
  2. zebra_day/bin/fetch_zebra_config.py +15 -0
  3. zebra_day/bin/generate_coord_grid_zpl.py +50 -0
  4. zebra_day/bin/print_zpl_from_file.py +21 -0
  5. zebra_day/bin/probe_new_label_dimensions.py +75 -0
  6. zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +2 -1
  7. zebra_day/bin/zserve.py +701 -259
  8. zebra_day/cli/__init__.py +240 -0
  9. zebra_day/cli/cognito.py +121 -0
  10. zebra_day/cli/gui.py +255 -0
  11. zebra_day/cli/printer.py +168 -0
  12. zebra_day/cli/template.py +176 -0
  13. zebra_day/cmd_mgr.py +35 -0
  14. zebra_day/etc/label_styles/blank.zpl +0 -0
  15. zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
  16. zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
  17. zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
  18. zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
  19. zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
  20. zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
  21. zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
  22. zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
  23. zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
  24. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
  25. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
  26. zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
  27. zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
  28. zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
  29. zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
  30. zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
  31. zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
  32. zebra_day/etc/label_styles/tube_2inX0.5in.zpl +15 -0
  33. zebra_day/etc/label_styles/tube_2inX0.5inHD.zpl +15 -0
  34. zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
  35. zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
  36. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
  37. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
  38. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
  39. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
  40. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
  41. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
  42. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
  43. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
  44. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
  45. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
  46. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
  47. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
  48. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
  49. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
  50. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
  51. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
  52. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
  53. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
  54. zebra_day/etc/old_printer_config/{2023-10-25_02:19:04.139607_printer_config.json → 2026-02-01_01:51:29.739070_printer_config.json} +4 -3
  55. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
  56. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
  57. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
  58. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
  59. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
  60. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
  61. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
  62. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
  63. zebra_day/etc/printer_config.json +3 -54
  64. zebra_day/etc/printer_config.template.json +3 -2
  65. zebra_day/etc/tmp_printers0.json +5 -0
  66. zebra_day/etc/tmp_printers374.json +5 -0
  67. zebra_day/etc/tmp_printers383.json +5 -0
  68. zebra_day/etc/tmp_printers450.json +5 -0
  69. zebra_day/etc/tmp_printers504.json +5 -0
  70. zebra_day/etc/tmp_printers608.json +5 -0
  71. zebra_day/etc/tmp_printers657.json +5 -0
  72. zebra_day/etc/tmp_printers838.json +5 -0
  73. zebra_day/etc/tmp_printers839.json +5 -0
  74. zebra_day/etc/tmp_printers933.json +5 -0
  75. zebra_day/etc/tmp_printers957.json +5 -0
  76. zebra_day/exceptions.py +88 -0
  77. zebra_day/files/hold +0 -0
  78. zebra_day/files/test_png_17696.png +0 -0
  79. zebra_day/files/test_png_23477.png +0 -0
  80. zebra_day/files/test_png_28157.png +0 -0
  81. zebra_day/files/test_png_35832.png +0 -0
  82. zebra_day/files/test_png_36400.png +0 -0
  83. zebra_day/files/test_png_40816.png +0 -0
  84. zebra_day/files/test_png_49564.png +0 -0
  85. zebra_day/files/test_png_53848.png +0 -0
  86. zebra_day/files/test_png_62542.png +0 -0
  87. zebra_day/files/test_png_91597.png +0 -0
  88. zebra_day/files/test_png_93633.png +0 -0
  89. zebra_day/files/tmpbjo3k7q1.png +0 -0
  90. zebra_day/files/tmpigtr4pwy.png +0 -0
  91. zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
  92. zebra_day/logging_config.py +74 -0
  93. zebra_day/logs/.hold +0 -0
  94. zebra_day/logs/print_requests.log +2 -0
  95. zebra_day/paths.py +143 -0
  96. zebra_day/print_mgr.py +489 -103
  97. zebra_day/static/datschund.css +63 -43
  98. zebra_day/static/datschund.png +0 -0
  99. zebra_day/static/daylily.png +0 -0
  100. zebra_day/static/favicon.svg +20 -0
  101. zebra_day/static/general.css +99 -0
  102. zebra_day/static/js/zebra_modern.js +172 -0
  103. zebra_day/static/lsmc.css +354 -0
  104. zebra_day/static/moon.jpeg +0 -0
  105. zebra_day/static/oakland.css +0 -32
  106. zebra_day/static/popday_daylily.css +1 -1
  107. zebra_day/static/style.css +39 -0
  108. zebra_day/static/zebra_modern.css +771 -0
  109. zebra_day/templates/base.html +36 -0
  110. zebra_day/templates/bpr.html +72 -0
  111. zebra_day/templates/build_new_config.html +36 -0
  112. zebra_day/templates/build_print_request.html +32 -0
  113. zebra_day/templates/chg_ui_style.html +19 -0
  114. zebra_day/templates/edit_template.html +128 -0
  115. zebra_day/templates/edit_zpl.html +37 -0
  116. zebra_day/templates/index.html +82 -0
  117. zebra_day/templates/legacy/base.html +37 -0
  118. zebra_day/templates/legacy/bpr.html +72 -0
  119. zebra_day/templates/legacy/build_new_config.html +36 -0
  120. zebra_day/templates/legacy/build_print_request.html +32 -0
  121. zebra_day/templates/legacy/chg_ui_style.html +19 -0
  122. zebra_day/templates/legacy/edit_template.html +128 -0
  123. zebra_day/templates/legacy/edit_zpl.html +37 -0
  124. zebra_day/templates/legacy/index.html +82 -0
  125. zebra_day/templates/legacy/list_prior_configs.html +24 -0
  126. zebra_day/templates/legacy/print_result.html +30 -0
  127. zebra_day/templates/legacy/printer_details.html +25 -0
  128. zebra_day/templates/legacy/printer_status.html +70 -0
  129. zebra_day/templates/legacy/save_result.html +17 -0
  130. zebra_day/templates/legacy/send_print_request.html +34 -0
  131. zebra_day/templates/legacy/simple_print.html +94 -0
  132. zebra_day/templates/legacy/view_pstation_json.html +29 -0
  133. zebra_day/templates/list_prior_configs.html +24 -0
  134. zebra_day/templates/modern/base.html +98 -0
  135. zebra_day/templates/modern/config.html +141 -0
  136. zebra_day/templates/modern/dashboard.html +160 -0
  137. zebra_day/templates/modern/print_request.html +141 -0
  138. zebra_day/templates/modern/print_result.html +88 -0
  139. zebra_day/templates/modern/printer_detail.html +117 -0
  140. zebra_day/templates/modern/printers.html +133 -0
  141. zebra_day/templates/modern/save_result.html +46 -0
  142. zebra_day/templates/modern/template_editor.html +172 -0
  143. zebra_day/templates/modern/templates.html +122 -0
  144. zebra_day/templates/print_result.html +30 -0
  145. zebra_day/templates/printer_details.html +25 -0
  146. zebra_day/templates/printer_status.html +70 -0
  147. zebra_day/templates/save_result.html +17 -0
  148. zebra_day/templates/send_print_request.html +34 -0
  149. zebra_day/templates/simple_print.html +94 -0
  150. zebra_day/templates/view_pstation_json.html +29 -0
  151. zebra_day/web/__init__.py +9 -0
  152. zebra_day/web/app.py +171 -0
  153. zebra_day/web/auth.py +172 -0
  154. zebra_day/web/middleware.py +159 -0
  155. zebra_day/web/routers/__init__.py +2 -0
  156. zebra_day/web/routers/api.py +163 -0
  157. zebra_day/web/routers/ui.py +1051 -0
  158. zebra_day/zpl_renderer.py +273 -0
  159. zebra_day-1.0.2.dist-info/METADATA +786 -0
  160. zebra_day-1.0.2.dist-info/RECORD +179 -0
  161. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/WHEEL +1 -1
  162. zebra_day-1.0.2.dist-info/entry_points.txt +4 -0
  163. zebra_day/etc/.blind +0 -1
  164. zebra_day/etc/current_style.txt +0 -1
  165. zebra_day/etc/label_styles/test_2inX1in.zpl +0 -22
  166. zebra_day/etc/label_styles/tmps/labware_2inX1in.na.2023-10-24_12:49:21.045127.zpl +0 -21
  167. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.8888.2023-10-24_16:02:16.443911.zpl +0 -21
  168. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.zpl +0 -21
  169. zebra_day/etc/label_styles/tmps/test_2inX1in.na.2023-10-24_12:45:37.002774.zpl +0 -22
  170. zebra_day/etc/old_printer_config/2023-10-24_16:06:06.931764_printer_config.json +0 -67
  171. zebra_day/etc/printer_config.json~ +0 -67
  172. zebra_day/files/tmp_2olihg4.png +0 -0
  173. zebra_day/files/tmpveojoyvn.png +0 -0
  174. zebra_day/files/zpl_label_labware_2inX1in_2023-10-25_02:30:08.093631.png +0 -0
  175. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_15:54:29.343124.png +0 -0
  176. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_16:01:45.670132.png +0 -0
  177. zebra_day/static/beyonce.css +0 -227
  178. zebra_day/static/datschund_on_moon.css +0 -164
  179. zebra_day/static/medicalsci.css +0 -144
  180. zebra_day/static/moar_zebra.css +0 -133
  181. zebra_day/static/popday.css +0 -140
  182. zebra_day/static/popday_dark.css +0 -140
  183. zebra_day/static/popday_dog.css +0 -140
  184. zebra_day-0.0.32.dist-info/METADATA +0 -14
  185. zebra_day-0.0.32.dist-info/RECORD +0 -53
  186. zebra_day-0.0.32.dist-info/entry_points.txt +0 -2
  187. /zebra_day/{etc/label_styles/blank_0inX0in.zpl → bin/__init__.py} +0 -0
  188. /zebra_day/etc/label_styles/{labware_2inX1in.zpl → generic_2inX1in.zpl} +0 -0
  189. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info/licenses}/LICENSE +0 -0
  190. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,240 @@
1
+ """zebra_day CLI - Zebra Printer Fleet Management CLI using Typer."""
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from zebra_day import paths as xdg
13
+ from zebra_day.cli.gui import gui_app
14
+ from zebra_day.cli.printer import printer_app
15
+ from zebra_day.cli.template import template_app
16
+ from zebra_day.cli.cognito import cognito_app
17
+
18
+ console = Console()
19
+
20
+ app = typer.Typer(
21
+ name="zday",
22
+ help="zebra_day - Zebra Printer Fleet Management CLI",
23
+ add_completion=True,
24
+ no_args_is_help=True,
25
+ )
26
+
27
+ # Register subcommand groups
28
+ app.add_typer(gui_app, name="gui", help="Web UI server management")
29
+ app.add_typer(printer_app, name="printer", help="Printer fleet management")
30
+ app.add_typer(template_app, name="template", help="ZPL template management")
31
+ app.add_typer(cognito_app, name="cognito", help="Cognito authentication management")
32
+
33
+
34
+ def _get_version() -> str:
35
+ """Get zebra_day version."""
36
+ try:
37
+ from importlib.metadata import version
38
+ return version("zebra_day")
39
+ except Exception:
40
+ return "dev"
41
+
42
+
43
+ @app.command("version")
44
+ def version():
45
+ """Show zebra_day version."""
46
+ console.print(f"zebra_day [cyan]{_get_version()}[/cyan]")
47
+
48
+
49
+ @app.command("info")
50
+ def info():
51
+ """Show zebra_day configuration and status."""
52
+ table = Table(title="zebra_day Info")
53
+ table.add_column("Property", style="cyan")
54
+ table.add_column("Value")
55
+
56
+ # Version
57
+ table.add_row("Version", _get_version())
58
+ table.add_row("Python", sys.version.split()[0])
59
+
60
+ # XDG Paths
61
+ table.add_row("Config Dir", str(xdg.get_config_dir()))
62
+ table.add_row("Data Dir", str(xdg.get_data_dir()))
63
+ table.add_row("Logs Dir", str(xdg.get_logs_dir()))
64
+ table.add_row("Cache Dir", str(xdg.get_cache_dir()))
65
+
66
+ # Printer config
67
+ printer_cfg = xdg.get_printer_config_path()
68
+ if printer_cfg.exists():
69
+ table.add_row("Printer Config", f"[green]{printer_cfg}[/green]")
70
+ else:
71
+ table.add_row("Printer Config", f"[yellow]not found[/yellow] [dim]({printer_cfg})[/dim]")
72
+
73
+ # Check if GUI server is running
74
+ pid_file = xdg.get_state_dir() / "gui.pid"
75
+ if pid_file.exists():
76
+ try:
77
+ pid = int(pid_file.read_text().strip())
78
+ os.kill(pid, 0)
79
+ table.add_row("GUI Server", f"[green]Running[/green] (PID {pid})")
80
+ except (ValueError, ProcessLookupError, PermissionError):
81
+ table.add_row("GUI Server", "[dim]Stopped[/dim]")
82
+ else:
83
+ table.add_row("GUI Server", "[dim]Stopped[/dim]")
84
+
85
+ console.print(table)
86
+
87
+
88
+ @app.command("status")
89
+ def status(
90
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
91
+ ):
92
+ """Show printer fleet status, network connectivity, and service health."""
93
+ import json as json_mod
94
+
95
+ status_data = {
96
+ "gui_server": {"running": False, "pid": None, "url": None},
97
+ "printers": {"configured": 0, "labs": []},
98
+ "config": {"exists": False, "path": None},
99
+ }
100
+
101
+ # Check GUI server
102
+ pid_file = xdg.get_state_dir() / "gui.pid"
103
+ if pid_file.exists():
104
+ try:
105
+ pid = int(pid_file.read_text().strip())
106
+ os.kill(pid, 0)
107
+ status_data["gui_server"]["running"] = True
108
+ status_data["gui_server"]["pid"] = pid
109
+ status_data["gui_server"]["url"] = "http://0.0.0.0:8118"
110
+ except (ValueError, ProcessLookupError, PermissionError):
111
+ pass
112
+
113
+ # Check printer config
114
+ printer_cfg = xdg.get_printer_config_path()
115
+ status_data["config"]["path"] = str(printer_cfg)
116
+ if printer_cfg.exists():
117
+ status_data["config"]["exists"] = True
118
+ try:
119
+ import zebra_day.print_mgr as zdpm
120
+ zp = zdpm.zpl()
121
+ if hasattr(zp, "printers") and "labs" in zp.printers:
122
+ labs = list(zp.printers["labs"].keys())
123
+ status_data["printers"]["labs"] = labs
124
+ total_printers = sum(
125
+ len([k for k in zp.printers["labs"][lab].keys()])
126
+ for lab in labs
127
+ )
128
+ status_data["printers"]["configured"] = total_printers
129
+ except Exception:
130
+ pass
131
+
132
+ if json_output:
133
+ console.print(json_mod.dumps(status_data, indent=2))
134
+ return
135
+
136
+ # Human-readable output
137
+ console.print("\n[bold]Service Status[/bold]")
138
+ if status_data["gui_server"]["running"]:
139
+ console.print(f" [green]●[/green] GUI Server: [green]Running[/green] (PID {status_data['gui_server']['pid']})")
140
+ console.print(f" URL: [cyan]{status_data['gui_server']['url']}[/cyan]")
141
+ else:
142
+ console.print(" [dim]○[/dim] GUI Server: [dim]Not running[/dim]")
143
+
144
+ console.print("\n[bold]Printer Fleet[/bold]")
145
+ if status_data["config"]["exists"]:
146
+ console.print(f" [green]●[/green] Config: [green]Loaded[/green]")
147
+ console.print(f" Printers: {status_data['printers']['configured']}")
148
+ console.print(f" Labs: {', '.join(status_data['printers']['labs']) or 'none'}")
149
+ else:
150
+ console.print(" [yellow]○[/yellow] Config: [yellow]Not found[/yellow]")
151
+ console.print(f" Run [cyan]zday bootstrap[/cyan] to initialize")
152
+ console.print()
153
+
154
+
155
+ @app.command("bootstrap")
156
+ def bootstrap(
157
+ ip_stub: Optional[str] = typer.Option(None, "--ip-stub", "-i", help="IP stub for printer scan (e.g., 192.168.1)"),
158
+ skip_scan: bool = typer.Option(False, "--skip-scan", "-s", help="Skip printer network scan"),
159
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
160
+ ):
161
+ """Initialize configuration, scan for printers, and setup first-time environment.
162
+
163
+ This is the recommended first-time setup command. It will:
164
+ 1. Create XDG configuration directories
165
+ 2. Initialize printer configuration
166
+ 3. Scan the network for Zebra printers (unless --skip-scan)
167
+ """
168
+ import json as json_mod
169
+ import time
170
+ import socket
171
+
172
+ result = {
173
+ "config_dir": str(xdg.get_config_dir()),
174
+ "data_dir": str(xdg.get_data_dir()),
175
+ "printers_found": 0,
176
+ "labs": [],
177
+ }
178
+
179
+ if not json_output:
180
+ console.print("\n[bold cyan]zebra_day Bootstrap[/bold cyan]\n")
181
+ console.print("[green]✓[/green] Config directory: " + result["config_dir"])
182
+ console.print("[green]✓[/green] Data directory: " + result["data_dir"])
183
+
184
+ if skip_scan:
185
+ if not json_output:
186
+ console.print("[dim]Skipping printer scan (--skip-scan)[/dim]")
187
+ else:
188
+ # Determine IP stub
189
+ if not ip_stub:
190
+ try:
191
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
192
+ s.connect(("8.8.8.8", 80))
193
+ local_ip = s.getsockname()[0]
194
+ s.close()
195
+ ip_stub = ".".join(local_ip.split(".")[:-1])
196
+ except Exception:
197
+ ip_stub = "192.168.1"
198
+
199
+ if not json_output:
200
+ console.print(f"\n[cyan]→[/cyan] Scanning network for Zebra printers ({ip_stub}.*)...")
201
+ console.print("[dim] This may take a few minutes...[/dim]")
202
+
203
+ try:
204
+ import zebra_day.print_mgr as zdpm
205
+ zp = zdpm.zpl()
206
+ zp.probe_zebra_printers_add_to_printers_json(ip_stub=ip_stub)
207
+
208
+ if hasattr(zp, "printers") and "labs" in zp.printers:
209
+ for lab in zp.printers["labs"]:
210
+ printers_in_lab = len([k for k in zp.printers["labs"][lab].keys()])
211
+ result["printers_found"] += printers_in_lab
212
+ result["labs"].append(lab)
213
+
214
+ if not json_output:
215
+ console.print(f"[green]✓[/green] Scan complete: {result['printers_found']} printer(s) found")
216
+ if result["labs"]:
217
+ console.print(f" Labs: {', '.join(result['labs'])}")
218
+ except Exception as e:
219
+ if not json_output:
220
+ console.print(f"[yellow]⚠[/yellow] Scan error: {e}")
221
+
222
+ if json_output:
223
+ console.print(json_mod.dumps(result, indent=2))
224
+ else:
225
+ console.print("\n[bold green]✓ Bootstrap complete![/bold green]")
226
+ console.print("\n[bold]Next steps:[/bold]")
227
+ console.print(" zday gui start Start the web UI")
228
+ console.print(" zday printer list Show configured printers")
229
+ console.print(" zday info Show configuration details")
230
+ console.print()
231
+
232
+
233
+ def main():
234
+ """Main CLI entry point."""
235
+ app()
236
+
237
+
238
+ if __name__ == "__main__":
239
+ raise SystemExit(main())
240
+
@@ -0,0 +1,121 @@
1
+ """Cognito authentication management commands for zebra_day CLI.
2
+
3
+ This module delegates to daylily-cognito if available, otherwise provides
4
+ basic status and info commands.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ console = Console()
15
+
16
+
17
+ def _is_cognito_available() -> bool:
18
+ """Check if daylily-cognito is installed."""
19
+ try:
20
+ from daylily_cognito.cli import cognito_app as _ # noqa: F401
21
+ return True
22
+ except ImportError:
23
+ return False
24
+
25
+
26
+ def _get_cognito_app() -> typer.Typer:
27
+ """Get the Cognito CLI app, either from daylily-cognito or a fallback."""
28
+ if _is_cognito_available():
29
+ # Import and return the full cognito CLI from daylily-cognito
30
+ from daylily_cognito.cli import cognito_app
31
+ return cognito_app
32
+ else:
33
+ # Return a minimal fallback app
34
+ return _create_fallback_app()
35
+
36
+
37
+ def _create_fallback_app() -> typer.Typer:
38
+ """Create a fallback cognito app with basic commands."""
39
+ app = typer.Typer(help="Cognito authentication management (limited - daylily-cognito not installed)")
40
+
41
+ @app.command("status")
42
+ def status():
43
+ """Show current Cognito authentication configuration."""
44
+ table = Table(title="Cognito Configuration")
45
+ table.add_column("Variable", style="cyan")
46
+ table.add_column("Value")
47
+ table.add_column("Status")
48
+
49
+ pool_id = os.environ.get("COGNITO_USER_POOL_ID")
50
+ client_id = os.environ.get("COGNITO_APP_CLIENT_ID")
51
+ region = os.environ.get("COGNITO_REGION", os.environ.get("AWS_DEFAULT_REGION"))
52
+
53
+ if pool_id:
54
+ # Truncate for display
55
+ display = pool_id[:15] + "..." if len(pool_id) > 15 else pool_id
56
+ table.add_row("COGNITO_USER_POOL_ID", display, "[green]Set[/green]")
57
+ else:
58
+ table.add_row("COGNITO_USER_POOL_ID", "-", "[yellow]Not set[/yellow]")
59
+
60
+ if client_id:
61
+ display = client_id[:15] + "..." if len(client_id) > 15 else client_id
62
+ table.add_row("COGNITO_APP_CLIENT_ID", display, "[green]Set[/green]")
63
+ else:
64
+ table.add_row("COGNITO_APP_CLIENT_ID", "-", "[yellow]Not set[/yellow]")
65
+
66
+ if region:
67
+ table.add_row("COGNITO_REGION", region, "[green]Set[/green]")
68
+ else:
69
+ table.add_row("COGNITO_REGION", "-", "[yellow]Not set[/yellow]")
70
+
71
+ console.print(table)
72
+
73
+ # Summary
74
+ if pool_id and client_id:
75
+ console.print("\n[green]✓[/green] Cognito is configured")
76
+ console.print(" Start server with: [cyan]zday gui start --auth cognito[/cyan]")
77
+ else:
78
+ console.print("\n[yellow]⚠[/yellow] Cognito is not fully configured")
79
+ console.print(" Set environment variables or install daylily-cognito for full management")
80
+
81
+ @app.command("info")
82
+ def info():
83
+ """Display information about Cognito setup requirements."""
84
+ console.print("\n[bold]Cognito Authentication Setup[/bold]\n")
85
+ console.print("To enable Cognito authentication for zebra_day:\n")
86
+
87
+ console.print("[bold]1. Install auth dependencies:[/bold]")
88
+ console.print(" [cyan]pip install -e \".[auth]\"[/cyan]\n")
89
+
90
+ console.print("[bold]2. Set environment variables:[/bold]")
91
+ console.print(" [cyan]export COGNITO_USER_POOL_ID=your-pool-id[/cyan]")
92
+ console.print(" [cyan]export COGNITO_APP_CLIENT_ID=your-client-id[/cyan]")
93
+ console.print(" [cyan]export COGNITO_REGION=us-west-2[/cyan] # optional\n")
94
+
95
+ console.print("[bold]3. Start server with authentication:[/bold]")
96
+ console.print(" [cyan]zday gui start --auth cognito[/cyan]\n")
97
+
98
+ if not _is_cognito_available():
99
+ console.print("[dim]For full Cognito management (create, teardown), install daylily-cognito:[/dim]")
100
+ console.print("[dim] pip install daylily-cognito[/dim]")
101
+
102
+ @app.command("create")
103
+ def create():
104
+ """Create/configure a Cognito user pool (requires daylily-cognito)."""
105
+ console.print("[yellow]⚠[/yellow] This command requires daylily-cognito")
106
+ console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
107
+ raise typer.Exit(1)
108
+
109
+ @app.command("teardown")
110
+ def teardown():
111
+ """Remove Cognito configuration (requires daylily-cognito)."""
112
+ console.print("[yellow]⚠[/yellow] This command requires daylily-cognito")
113
+ console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
114
+ raise typer.Exit(1)
115
+
116
+ return app
117
+
118
+
119
+ # Export the cognito app - either the full version from daylily-cognito or the fallback
120
+ cognito_app = _get_cognito_app()
121
+
zebra_day/cli/gui.py ADDED
@@ -0,0 +1,255 @@
1
+ """GUI server management commands for zebra_day CLI."""
2
+
3
+ import os
4
+ import signal
5
+ import subprocess
6
+ import sys
7
+ import time
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Optional, Literal
11
+
12
+ import typer
13
+ from rich.console import Console
14
+
15
+ from zebra_day import paths as xdg
16
+
17
+ gui_app = typer.Typer(help="Web UI server management commands")
18
+ console = Console()
19
+
20
+ # PID and log file locations
21
+ STATE_DIR = xdg.get_state_dir()
22
+ LOG_DIR = xdg.get_logs_dir()
23
+ PID_FILE = STATE_DIR / "gui.pid"
24
+
25
+
26
+ def _ensure_dirs():
27
+ """Ensure state and log directories exist."""
28
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
29
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
30
+
31
+
32
+ def _get_log_file() -> Path:
33
+ """Get timestamped log file path."""
34
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
35
+ return LOG_DIR / f"gui_{ts}.log"
36
+
37
+
38
+ def _get_latest_log() -> Optional[Path]:
39
+ """Get the most recent log file."""
40
+ logs = sorted(LOG_DIR.glob("gui_*.log"), reverse=True)
41
+ return logs[0] if logs else None
42
+
43
+
44
+ def _get_pid() -> Optional[int]:
45
+ """Get the running server PID if exists."""
46
+ if PID_FILE.exists():
47
+ try:
48
+ pid = int(PID_FILE.read_text().strip())
49
+ os.kill(pid, 0)
50
+ return pid
51
+ except (ValueError, ProcessLookupError, PermissionError):
52
+ PID_FILE.unlink(missing_ok=True)
53
+ return None
54
+
55
+
56
+ def _check_auth_dependencies() -> bool:
57
+ """Check if auth dependencies are available."""
58
+ try:
59
+ import jose # noqa: F401
60
+ return True
61
+ except ImportError:
62
+ return False
63
+
64
+
65
+ @gui_app.command("start")
66
+ def start(
67
+ port: int = typer.Option(8118, "--port", "-p", help="Port to run the server on"),
68
+ host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind to"),
69
+ auth: str = typer.Option("none", "--auth", "-a", help="Authentication mode: none or cognito"),
70
+ reload: bool = typer.Option(False, "--reload", "-r", help="Enable auto-reload (foreground)"),
71
+ background: bool = typer.Option(True, "--background/--foreground", "-b/-f", help="Run in background"),
72
+ ):
73
+ """Start the zebra_day web UI server."""
74
+ _ensure_dirs()
75
+
76
+ # Validate auth option
77
+ if auth not in ("none", "cognito"):
78
+ console.print(f"[red]✗[/red] Invalid auth mode: {auth}. Use 'none' or 'cognito'.")
79
+ raise typer.Exit(1)
80
+
81
+ # Check if already running
82
+ pid = _get_pid()
83
+ if pid:
84
+ console.print(f"[yellow]⚠[/yellow] Server already running (PID {pid})")
85
+ console.print(f" URL: [cyan]http://{host}:{port}[/cyan]")
86
+ return
87
+
88
+ # Check auth dependencies if cognito mode
89
+ if auth == "cognito":
90
+ if not _check_auth_dependencies():
91
+ console.print("[red]✗[/red] Authentication requested but python-jose is not installed")
92
+ console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
93
+ raise typer.Exit(1)
94
+
95
+ # Check required env vars
96
+ missing = []
97
+ if not os.environ.get("COGNITO_USER_POOL_ID"):
98
+ missing.append("COGNITO_USER_POOL_ID")
99
+ if not os.environ.get("COGNITO_APP_CLIENT_ID"):
100
+ missing.append("COGNITO_APP_CLIENT_ID")
101
+ if missing:
102
+ console.print("[red]✗[/red] Cognito auth enabled but environment variables missing:")
103
+ for var in missing:
104
+ console.print(f" • {var}")
105
+ raise typer.Exit(1)
106
+ console.print("[green]✓[/green] Cognito authentication enabled")
107
+
108
+ # Build command
109
+ cmd = [
110
+ sys.executable,
111
+ "-c",
112
+ f"from zebra_day.web.app import run_server; run_server(host='{host}', port={port}, reload={reload}, auth='{auth}')",
113
+ ]
114
+
115
+ # Set up environment
116
+ env = os.environ.copy()
117
+ env["PYTHONUNBUFFERED"] = "1"
118
+ env["ZEBRA_DAY_AUTH_MODE"] = auth
119
+
120
+ if reload:
121
+ background = False
122
+ console.print("[dim]Auto-reload enabled (foreground mode)[/dim]")
123
+
124
+ if background:
125
+ log_file = _get_log_file()
126
+ log_f = open(log_file, "w", buffering=1)
127
+
128
+ proc = subprocess.Popen(
129
+ cmd,
130
+ stdout=log_f,
131
+ stderr=subprocess.STDOUT,
132
+ start_new_session=True,
133
+ cwd=Path.cwd(),
134
+ env=env,
135
+ )
136
+
137
+ time.sleep(2)
138
+ if proc.poll() is not None:
139
+ log_f.close()
140
+ console.print("[red]✗[/red] Server failed to start. Check logs:")
141
+ console.print(f" [dim]{log_file}[/dim]")
142
+ if log_file.exists():
143
+ content = log_file.read_text().strip()
144
+ if content:
145
+ console.print("\n[dim]--- Last error ---[/dim]")
146
+ for line in content.split("\n")[-10:]:
147
+ console.print(f" {line}")
148
+ raise typer.Exit(1)
149
+
150
+ PID_FILE.write_text(str(proc.pid))
151
+ console.print(f"[green]✓[/green] Server started (PID {proc.pid})")
152
+ console.print(f" URL: [cyan]http://{host}:{port}[/cyan]")
153
+ console.print(f" Logs: [dim]{log_file}[/dim]")
154
+ else:
155
+ console.print(f"[green]✓[/green] Starting server on [cyan]http://{host}:{port}[/cyan]")
156
+ console.print(" Press Ctrl+C to stop\n")
157
+ try:
158
+ result = subprocess.run(cmd, cwd=Path.cwd(), env=env)
159
+ if result.returncode != 0:
160
+ raise typer.Exit(result.returncode)
161
+ except KeyboardInterrupt:
162
+ console.print("\n[yellow]⚠[/yellow] Server stopped")
163
+
164
+
165
+ @gui_app.command("stop")
166
+ def stop():
167
+ """Stop the zebra_day web UI server."""
168
+ pid = _get_pid()
169
+ if not pid:
170
+ console.print("[yellow]⚠[/yellow] No server running")
171
+ return
172
+
173
+ try:
174
+ os.kill(pid, signal.SIGTERM)
175
+ for _ in range(10):
176
+ time.sleep(0.5)
177
+ try:
178
+ os.kill(pid, 0)
179
+ except ProcessLookupError:
180
+ break
181
+ else:
182
+ os.kill(pid, signal.SIGKILL)
183
+
184
+ PID_FILE.unlink(missing_ok=True)
185
+ console.print(f"[green]✓[/green] Server stopped (was PID {pid})")
186
+ except ProcessLookupError:
187
+ PID_FILE.unlink(missing_ok=True)
188
+ console.print("[yellow]⚠[/yellow] Server was not running")
189
+ except PermissionError:
190
+ console.print(f"[red]✗[/red] Permission denied stopping PID {pid}")
191
+ raise typer.Exit(1)
192
+
193
+
194
+ @gui_app.command("status")
195
+ def status():
196
+ """Check the status of the zebra_day web UI server."""
197
+ pid = _get_pid()
198
+ if pid:
199
+ log_file = _get_latest_log()
200
+ console.print(f"[green]●[/green] Server is [green]running[/green] (PID {pid})")
201
+ console.print(f" URL: [cyan]http://0.0.0.0:8118[/cyan]")
202
+ if log_file:
203
+ console.print(f" Logs: [dim]{log_file}[/dim]")
204
+ else:
205
+ console.print("[dim]○[/dim] Server is [dim]not running[/dim]")
206
+
207
+
208
+ @gui_app.command("logs")
209
+ def logs(
210
+ lines: int = typer.Option(50, "--tail", "-n", help="Number of lines to show"),
211
+ follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
212
+ all_logs: bool = typer.Option(False, "--all", "-a", help="List all log files"),
213
+ ):
214
+ """View zebra_day web UI server logs."""
215
+ if all_logs:
216
+ log_files = sorted(LOG_DIR.glob("gui_*.log"), reverse=True)
217
+ if not log_files:
218
+ console.print("[yellow]⚠[/yellow] No log files found.")
219
+ return
220
+ console.print(f"[bold]Server log files ({len(log_files)}):[/bold]")
221
+ for lf in log_files[:20]:
222
+ size = lf.stat().st_size
223
+ console.print(f" {lf.name} [dim]({size:,} bytes)[/dim]")
224
+ return
225
+
226
+ log_file = _get_latest_log()
227
+ if not log_file:
228
+ console.print("[yellow]⚠[/yellow] No log file found. Start the server first.")
229
+ return
230
+
231
+ if follow:
232
+ console.print(f"[dim]Following {log_file.name} (Ctrl+C to stop)[/dim]\n")
233
+ try:
234
+ subprocess.run(["tail", "-f", "-n", str(lines), str(log_file)])
235
+ except KeyboardInterrupt:
236
+ console.print("\n")
237
+ else:
238
+ console.print(f"[dim]Showing last {lines} lines of {log_file.name}[/dim]\n")
239
+ try:
240
+ subprocess.run(["tail", "-n", str(lines), str(log_file)])
241
+ except Exception as e:
242
+ console.print(f"[red]✗[/red] Error reading log: {e}")
243
+
244
+
245
+ @gui_app.command("restart")
246
+ def restart(
247
+ port: int = typer.Option(8118, "--port", "-p", help="Port to run the server on"),
248
+ host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind to"),
249
+ auth: str = typer.Option("none", "--auth", "-a", help="Authentication mode: none or cognito"),
250
+ ):
251
+ """Restart the zebra_day web UI server."""
252
+ stop()
253
+ time.sleep(1)
254
+ start(port=port, host=host, auth=auth, reload=False, background=True)
255
+