zebra-day 1.0.2__py3-none-any.whl → 2.1.4__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 (174) hide show
  1. zebra_day/__init__.py +7 -2
  2. zebra_day/_version.py +1 -0
  3. zebra_day/cli/__init__.py +80 -30
  4. zebra_day/cli/cognito.py +15 -9
  5. zebra_day/cli/gui.py +101 -13
  6. zebra_day/cli/printer.py +34 -27
  7. zebra_day/cli/template.py +19 -15
  8. zebra_day/cmd_mgr.py +3 -6
  9. zebra_day/docs/gx420d-gx430d-ug-en.pdf +0 -0
  10. zebra_day/docs/hardware_config_guide.md +149 -0
  11. zebra_day/docs/programatic_guide.md +181 -0
  12. zebra_day/docs/qln420_zebra_manual.pdf +0 -0
  13. zebra_day/docs/uid_screed_light.md +38 -0
  14. zebra_day/docs/zd620-zd420-ug-en.pdf +0 -0
  15. zebra_day/docs/zebra_day_ui_guide.md +194 -0
  16. zebra_day/etc/printer_config.json +5 -11
  17. zebra_day/etc/printer_config.template.json +5 -11
  18. zebra_day/etc/tmp_printers120.json +10 -0
  19. zebra_day/etc/tmp_printers139.json +10 -0
  20. zebra_day/etc/tmp_printers145.json +10 -0
  21. zebra_day/etc/tmp_printers147.json +10 -0
  22. zebra_day/etc/tmp_printers207.json +10 -0
  23. zebra_day/etc/tmp_printers34.json +10 -0
  24. zebra_day/etc/tmp_printers389.json +10 -0
  25. zebra_day/etc/tmp_printers398.json +10 -0
  26. zebra_day/etc/tmp_printers437.json +10 -0
  27. zebra_day/etc/tmp_printers439.json +10 -0
  28. zebra_day/etc/tmp_printers440.json +10 -0
  29. zebra_day/etc/tmp_printers469.json +10 -0
  30. zebra_day/etc/tmp_printers485.json +10 -0
  31. zebra_day/etc/tmp_printers508.json +10 -0
  32. zebra_day/etc/tmp_printers531.json +10 -0
  33. zebra_day/etc/tmp_printers540.json +10 -0
  34. zebra_day/etc/tmp_printers542.json +10 -0
  35. zebra_day/etc/tmp_printers543.json +10 -0
  36. zebra_day/etc/tmp_printers552.json +10 -0
  37. zebra_day/etc/tmp_printers715.json +10 -0
  38. zebra_day/etc/tmp_printers835.json +10 -0
  39. zebra_day/etc/tmp_printers842.json +10 -0
  40. zebra_day/etc/tmp_printers931.json +10 -0
  41. zebra_day/etc/tmp_printers969.json +10 -0
  42. zebra_day/etc/tmp_printers972.json +10 -0
  43. zebra_day/exceptions.py +1 -1
  44. zebra_day/files/blank_preview.png +0 -0
  45. zebra_day/files/corners_20cmX30cm_preview.png +0 -0
  46. zebra_day/files/corners_smallTube_preview.png +0 -0
  47. zebra_day/files/generic_2inX1in_preview.png +0 -0
  48. zebra_day/files/test_png_12020.png +0 -0
  49. zebra_day/files/test_png_12352.png +0 -0
  50. zebra_day/files/test_png_15472.png +0 -0
  51. zebra_day/files/test_png_24493.png +0 -0
  52. zebra_day/files/test_png_2897.png +0 -0
  53. zebra_day/files/test_png_30069.png +0 -0
  54. zebra_day/files/test_png_31690.png +0 -0
  55. zebra_day/files/test_png_33804.png +0 -0
  56. zebra_day/files/test_png_34737.png +0 -0
  57. zebra_day/files/test_png_4161.png +0 -0
  58. zebra_day/files/test_png_44748.png +0 -0
  59. zebra_day/files/test_png_4635.png +0 -0
  60. zebra_day/files/test_png_47791.png +0 -0
  61. zebra_day/files/test_png_47799.png +0 -0
  62. zebra_day/files/test_png_55588.png +0 -0
  63. zebra_day/files/test_png_56349.png +0 -0
  64. zebra_day/files/test_png_58809.png +0 -0
  65. zebra_day/files/test_png_5936.png +0 -0
  66. zebra_day/files/test_png_64110.png +0 -0
  67. zebra_day/files/test_png_64891.png +0 -0
  68. zebra_day/files/test_png_67242.png +0 -0
  69. zebra_day/files/test_png_69002.png +0 -0
  70. zebra_day/files/test_png_70065.png +0 -0
  71. zebra_day/files/test_png_72366.png +0 -0
  72. zebra_day/files/test_png_77793.png +0 -0
  73. zebra_day/files/test_png_89893.png +0 -0
  74. zebra_day/files/test_png_9572.png +0 -0
  75. zebra_day/files/tube_20mmX30mmA_preview.png +0 -0
  76. zebra_day/imgs/.hold +0 -0
  77. zebra_day/imgs/bar_ltpurp.png +0 -0
  78. zebra_day/imgs/bar_purp.png +0 -0
  79. zebra_day/imgs/bar_purp3.png +0 -0
  80. zebra_day/imgs/bar_red.png +0 -0
  81. zebra_day/imgs/legacy/UBC_gantt_chart.png +0 -0
  82. zebra_day/imgs/legacy/gx420d_network_config.png +0 -0
  83. zebra_day/imgs/legacy/gx420d_printer_config.png +0 -0
  84. zebra_day/imgs/legacy/ngrok.png +0 -0
  85. zebra_day/imgs/legacy/printer_details.png +0 -0
  86. zebra_day/imgs/legacy/quick_start_test_label.png +0 -0
  87. zebra_day/imgs/legacy/quick_start_test_label2.png +0 -0
  88. zebra_day/imgs/legacy/zd620_network_config.png +0 -0
  89. zebra_day/imgs/legacy/zd620_printer_config.png +0 -0
  90. zebra_day/imgs/legacy/zday_quick_gui.png +0 -0
  91. zebra_day/imgs/legacy/zebra_day_alt_css_dog.png +0 -0
  92. zebra_day/imgs/legacy/zebra_day_alt_css_flower.png +0 -0
  93. zebra_day/imgs/legacy/zebra_day_alt_css_main.png +0 -0
  94. zebra_day/imgs/legacy/zebra_day_available_zpl_templates.png +0 -0
  95. zebra_day/imgs/legacy/zebra_day_bkup_pconfig.png +0 -0
  96. zebra_day/imgs/legacy/zebra_day_home.png +0 -0
  97. zebra_day/imgs/legacy/zebra_day_manual_print.png +0 -0
  98. zebra_day/imgs/legacy/zebra_day_printer_fleet_json.png +0 -0
  99. zebra_day/imgs/legacy/zebra_day_quick_ex.png +0 -0
  100. zebra_day/imgs/legacy/zebra_day_zpl_template_IRLa.png +0 -0
  101. zebra_day/imgs/legacy/zebra_day_zpl_template_IRLb.png +0 -0
  102. zebra_day/imgs/ui_api_docs.png +0 -0
  103. zebra_day/imgs/ui_config.png +0 -0
  104. zebra_day/imgs/ui_dashboard.png +0 -0
  105. zebra_day/imgs/ui_print_request.png +0 -0
  106. zebra_day/imgs/ui_printers.png +0 -0
  107. zebra_day/imgs/ui_templates.png +0 -0
  108. zebra_day/logging_config.py +4 -9
  109. zebra_day/mkcert.py +157 -0
  110. zebra_day/paths.py +1 -2
  111. zebra_day/print_mgr.py +261 -185
  112. zebra_day/templates/modern/config.html +7 -0
  113. zebra_day/templates/modern/config_backups.html +59 -0
  114. zebra_day/templates/modern/config_editor.html +95 -0
  115. zebra_day/templates/modern/config_new.html +93 -0
  116. zebra_day/templates/modern/print_request.html +70 -8
  117. zebra_day/templates/modern/printer_detail.html +161 -34
  118. zebra_day/templates/modern/printers.html +17 -6
  119. zebra_day/templates/modern/template_editor.html +7 -4
  120. zebra_day/web/__init__.py +1 -1
  121. zebra_day/web/app.py +99 -17
  122. zebra_day/web/auth.py +17 -15
  123. zebra_day/web/middleware.py +8 -5
  124. zebra_day/web/routers/__init__.py +0 -1
  125. zebra_day/web/routers/api.py +330 -31
  126. zebra_day/web/routers/ui.py +174 -591
  127. zebra_day/zpl_renderer.py +45 -34
  128. {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/METADATA +144 -74
  129. zebra_day-2.1.4.dist-info/RECORD +240 -0
  130. zebra_day/bin/fetch_zebra_config.py +0 -15
  131. zebra_day/bin/generate_coord_grid_zpl.py +0 -50
  132. zebra_day/bin/print_zpl_from_file.py +0 -21
  133. zebra_day/bin/probe_new_label_dimensions.py +0 -75
  134. zebra_day/bin/scan_for_networed_zebra_printers.py +0 -23
  135. zebra_day/bin/scan_for_networed_zebra_printers_arp_scan.sh +0 -1
  136. zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +0 -30
  137. zebra_day/bin/zserve.py +0 -1062
  138. zebra_day/templates/base.html +0 -36
  139. zebra_day/templates/bpr.html +0 -72
  140. zebra_day/templates/build_new_config.html +0 -36
  141. zebra_day/templates/build_print_request.html +0 -32
  142. zebra_day/templates/chg_ui_style.html +0 -19
  143. zebra_day/templates/edit_template.html +0 -128
  144. zebra_day/templates/edit_zpl.html +0 -37
  145. zebra_day/templates/index.html +0 -82
  146. zebra_day/templates/legacy/base.html +0 -37
  147. zebra_day/templates/legacy/bpr.html +0 -72
  148. zebra_day/templates/legacy/build_new_config.html +0 -36
  149. zebra_day/templates/legacy/build_print_request.html +0 -32
  150. zebra_day/templates/legacy/chg_ui_style.html +0 -19
  151. zebra_day/templates/legacy/edit_template.html +0 -128
  152. zebra_day/templates/legacy/edit_zpl.html +0 -37
  153. zebra_day/templates/legacy/index.html +0 -82
  154. zebra_day/templates/legacy/list_prior_configs.html +0 -24
  155. zebra_day/templates/legacy/print_result.html +0 -30
  156. zebra_day/templates/legacy/printer_details.html +0 -25
  157. zebra_day/templates/legacy/printer_status.html +0 -70
  158. zebra_day/templates/legacy/save_result.html +0 -17
  159. zebra_day/templates/legacy/send_print_request.html +0 -34
  160. zebra_day/templates/legacy/simple_print.html +0 -94
  161. zebra_day/templates/legacy/view_pstation_json.html +0 -29
  162. zebra_day/templates/list_prior_configs.html +0 -24
  163. zebra_day/templates/print_result.html +0 -30
  164. zebra_day/templates/printer_details.html +0 -25
  165. zebra_day/templates/printer_status.html +0 -70
  166. zebra_day/templates/save_result.html +0 -17
  167. zebra_day/templates/send_print_request.html +0 -34
  168. zebra_day/templates/simple_print.html +0 -94
  169. zebra_day/templates/view_pstation_json.html +0 -29
  170. zebra_day-1.0.2.dist-info/RECORD +0 -179
  171. {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/WHEEL +0 -0
  172. {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/entry_points.txt +0 -0
  173. {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/licenses/LICENSE +0 -0
  174. {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/top_level.txt +0 -0
zebra_day/__init__.py CHANGED
@@ -1,8 +1,14 @@
1
1
  """
2
2
  zebra_day - A Python library to manage Zebra printer fleets and ZPL print requests.
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
 
7
+ try:
8
+ from zebra_day._version import __version__
9
+ except ImportError:
10
+ __version__ = "0.0.0.dev0"
11
+
6
12
  from zebra_day.exceptions import (
7
13
  ConfigError,
8
14
  ConfigFileNotFoundError,
@@ -18,10 +24,9 @@ from zebra_day.exceptions import (
18
24
  from zebra_day.logging_config import configure_logging, get_logger
19
25
 
20
26
  __all__ = [
21
- # Logging
27
+ "__version__",
22
28
  "configure_logging",
23
29
  "get_logger",
24
- # Exceptions
25
30
  "ZebraDayError",
26
31
  "PrinterConnectionError",
27
32
  "PrinterNotFoundError",
zebra_day/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2.1.4"
zebra_day/cli/__init__.py CHANGED
@@ -1,19 +1,21 @@
1
1
  """zebra_day CLI - Zebra Printer Fleet Management CLI using Typer."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
6
+ import socket
4
7
  import sys
5
- from pathlib import Path
6
- from typing import Optional
8
+ from typing import Any
7
9
 
8
10
  import typer
9
11
  from rich.console import Console
10
12
  from rich.table import Table
11
13
 
12
14
  from zebra_day import paths as xdg
15
+ from zebra_day.cli.cognito import cognito_app
13
16
  from zebra_day.cli.gui import gui_app
14
17
  from zebra_day.cli.printer import printer_app
15
18
  from zebra_day.cli.template import template_app
16
- from zebra_day.cli.cognito import cognito_app
17
19
 
18
20
  console = Console()
19
21
 
@@ -35,6 +37,7 @@ def _get_version() -> str:
35
37
  """Get zebra_day version."""
36
38
  try:
37
39
  from importlib.metadata import version
40
+
38
41
  return version("zebra_day")
39
42
  except Exception:
40
43
  return "dev"
@@ -92,7 +95,7 @@ def status(
92
95
  """Show printer fleet status, network connectivity, and service health."""
93
96
  import json as json_mod
94
97
 
95
- status_data = {
98
+ status_data: dict[str, dict[str, Any]] = {
96
99
  "gui_server": {"running": False, "pid": None, "url": None},
97
100
  "printers": {"configured": 0, "labs": []},
98
101
  "config": {"exists": False, "path": None},
@@ -117,14 +120,12 @@ def status(
117
120
  status_data["config"]["exists"] = True
118
121
  try:
119
122
  import zebra_day.print_mgr as zdpm
123
+
120
124
  zp = zdpm.zpl()
121
125
  if hasattr(zp, "printers") and "labs" in zp.printers:
122
126
  labs = list(zp.printers["labs"].keys())
123
127
  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
+ total_printers = sum(len(list(zp.printers["labs"][lab].keys())) for lab in labs)
128
129
  status_data["printers"]["configured"] = total_printers
129
130
  except Exception:
130
131
  pass
@@ -136,25 +137,29 @@ def status(
136
137
  # Human-readable output
137
138
  console.print("\n[bold]Service Status[/bold]")
138
139
  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(
141
+ f" [green]●[/green] GUI Server: [green]Running[/green] (PID {status_data['gui_server']['pid']})"
142
+ )
140
143
  console.print(f" URL: [cyan]{status_data['gui_server']['url']}[/cyan]")
141
144
  else:
142
145
  console.print(" [dim]○[/dim] GUI Server: [dim]Not running[/dim]")
143
146
 
144
147
  console.print("\n[bold]Printer Fleet[/bold]")
145
148
  if status_data["config"]["exists"]:
146
- console.print(f" [green]●[/green] Config: [green]Loaded[/green]")
149
+ console.print(" [green]●[/green] Config: [green]Loaded[/green]")
147
150
  console.print(f" Printers: {status_data['printers']['configured']}")
148
151
  console.print(f" Labs: {', '.join(status_data['printers']['labs']) or 'none'}")
149
152
  else:
150
153
  console.print(" [yellow]○[/yellow] Config: [yellow]Not found[/yellow]")
151
- console.print(f" Run [cyan]zday bootstrap[/cyan] to initialize")
154
+ console.print(" Run [cyan]zday bootstrap[/cyan] to initialize")
152
155
  console.print()
153
156
 
154
157
 
155
158
  @app.command("bootstrap")
156
159
  def bootstrap(
157
- ip_stub: Optional[str] = typer.Option(None, "--ip-stub", "-i", help="IP stub for printer scan (e.g., 192.168.1)"),
160
+ ip_stub: str | None = typer.Option(
161
+ None, "--ip-stub", "-i", help="IP stub for printer scan (e.g., 192.168.1)"
162
+ ),
158
163
  skip_scan: bool = typer.Option(False, "--skip-scan", "-s", help="Skip printer network scan"),
159
164
  json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
160
165
  ):
@@ -166,20 +171,16 @@ def bootstrap(
166
171
  3. Scan the network for Zebra printers (unless --skip-scan)
167
172
  """
168
173
  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
- }
174
+
175
+ config_dir = str(xdg.get_config_dir())
176
+ data_dir = str(xdg.get_data_dir())
177
+ printers_found = 0
178
+ labs: list[str] = []
178
179
 
179
180
  if not json_output:
180
181
  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"])
182
+ console.print("[green]✓[/green] Config directory: " + config_dir)
183
+ console.print("[green]✓[/green] Data directory: " + data_dir)
183
184
 
184
185
  if skip_scan:
185
186
  if not json_output:
@@ -202,24 +203,74 @@ def bootstrap(
202
203
 
203
204
  try:
204
205
  import zebra_day.print_mgr as zdpm
206
+
205
207
  zp = zdpm.zpl()
206
208
  zp.probe_zebra_printers_add_to_printers_json(ip_stub=ip_stub)
207
209
 
208
210
  if hasattr(zp, "printers") and "labs" in zp.printers:
209
211
  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)
212
+ printers_in_lab = len(list(zp.printers["labs"][lab].keys()))
213
+ printers_found += printers_in_lab
214
+ labs.append(lab)
213
215
 
214
216
  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'])}")
217
+ console.print(f"[green]✓[/green] Scan complete: {printers_found} printer(s) found")
218
+ if labs:
219
+ console.print(f" Labs: {', '.join(labs)}")
218
220
  except Exception as e:
219
221
  if not json_output:
220
222
  console.print(f"[yellow]⚠[/yellow] Scan error: {e}")
221
223
 
224
+ # Generate HTTPS certificates if mkcert is available
225
+ certs_generated = False
226
+ cert_path_str = None
227
+
228
+ if not json_output:
229
+ console.print("\n[cyan]→[/cyan] Checking HTTPS certificates...")
230
+
231
+ try:
232
+ from zebra_day import mkcert
233
+
234
+ if not mkcert.is_mkcert_installed():
235
+ if not json_output:
236
+ console.print("[yellow]⚠[/yellow] mkcert not installed")
237
+ console.print(
238
+ " Install with: [dim]brew install mkcert[/dim] (macOS) or "
239
+ "[dim]sudo apt install mkcert[/dim] (Ubuntu)"
240
+ )
241
+ elif not mkcert.is_ca_installed():
242
+ if not json_output:
243
+ console.print("[yellow]⚠[/yellow] mkcert CA not installed")
244
+ console.print(" Run: [dim]mkcert -install[/dim] (one-time, requires password)")
245
+ elif mkcert.certificates_exist():
246
+ if not json_output:
247
+ console.print(f"[green]✓[/green] Certificates exist: {mkcert.CERT_FILE}")
248
+ certs_generated = True
249
+ cert_path_str = str(mkcert.CERT_FILE)
250
+ else:
251
+ if not json_output:
252
+ console.print(" Generating certificates...")
253
+ if mkcert.generate_certificates():
254
+ if not json_output:
255
+ console.print(f"[green]✓[/green] Certificates generated: {mkcert.CERT_FILE}")
256
+ certs_generated = True
257
+ cert_path_str = str(mkcert.CERT_FILE)
258
+ else:
259
+ if not json_output:
260
+ console.print("[yellow]⚠[/yellow] Failed to generate certificates")
261
+ except Exception as e:
262
+ if not json_output:
263
+ console.print(f"[yellow]⚠[/yellow] Certificate check error: {e}")
264
+
222
265
  if json_output:
266
+ result = {
267
+ "config_dir": config_dir,
268
+ "data_dir": data_dir,
269
+ "printers_found": printers_found,
270
+ "labs": labs,
271
+ "https_certs_generated": certs_generated,
272
+ "cert_path": cert_path_str,
273
+ }
223
274
  console.print(json_mod.dumps(result, indent=2))
224
275
  else:
225
276
  console.print("\n[bold green]✓ Bootstrap complete![/bold green]")
@@ -237,4 +288,3 @@ def main():
237
288
 
238
289
  if __name__ == "__main__":
239
290
  raise SystemExit(main())
240
-
zebra_day/cli/cognito.py CHANGED
@@ -5,7 +5,6 @@ basic status and info commands.
5
5
  """
6
6
 
7
7
  import os
8
- from typing import Optional
9
8
 
10
9
  import typer
11
10
  from rich.console import Console
@@ -18,6 +17,7 @@ def _is_cognito_available() -> bool:
18
17
  """Check if daylily-cognito is installed."""
19
18
  try:
20
19
  from daylily_cognito.cli import cognito_app as _ # noqa: F401
20
+
21
21
  return True
22
22
  except ImportError:
23
23
  return False
@@ -28,7 +28,8 @@ def _get_cognito_app() -> typer.Typer:
28
28
  if _is_cognito_available():
29
29
  # Import and return the full cognito CLI from daylily-cognito
30
30
  from daylily_cognito.cli import cognito_app
31
- return cognito_app
31
+
32
+ return cognito_app # type: ignore[no-any-return]
32
33
  else:
33
34
  # Return a minimal fallback app
34
35
  return _create_fallback_app()
@@ -36,7 +37,9 @@ def _get_cognito_app() -> typer.Typer:
36
37
 
37
38
  def _create_fallback_app() -> typer.Typer:
38
39
  """Create a fallback cognito app with basic commands."""
39
- app = typer.Typer(help="Cognito authentication management (limited - daylily-cognito not installed)")
40
+ app = typer.Typer(
41
+ help="Cognito authentication management (limited - daylily-cognito not installed)"
42
+ )
40
43
 
41
44
  @app.command("status")
42
45
  def status():
@@ -76,7 +79,9 @@ def _create_fallback_app() -> typer.Typer:
76
79
  console.print(" Start server with: [cyan]zday gui start --auth cognito[/cyan]")
77
80
  else:
78
81
  console.print("\n[yellow]⚠[/yellow] Cognito is not fully configured")
79
- console.print(" Set environment variables or install daylily-cognito for full management")
82
+ console.print(
83
+ " Set environment variables or install daylily-cognito for full management"
84
+ )
80
85
 
81
86
  @app.command("info")
82
87
  def info():
@@ -85,7 +90,7 @@ def _create_fallback_app() -> typer.Typer:
85
90
  console.print("To enable Cognito authentication for zebra_day:\n")
86
91
 
87
92
  console.print("[bold]1. Install auth dependencies:[/bold]")
88
- console.print(" [cyan]pip install -e \".[auth]\"[/cyan]\n")
93
+ console.print(' [cyan]pip install -e ".[auth]"[/cyan]\n')
89
94
 
90
95
  console.print("[bold]2. Set environment variables:[/bold]")
91
96
  console.print(" [cyan]export COGNITO_USER_POOL_ID=your-pool-id[/cyan]")
@@ -96,21 +101,23 @@ def _create_fallback_app() -> typer.Typer:
96
101
  console.print(" [cyan]zday gui start --auth cognito[/cyan]\n")
97
102
 
98
103
  if not _is_cognito_available():
99
- console.print("[dim]For full Cognito management (create, teardown), install daylily-cognito:[/dim]")
104
+ console.print(
105
+ "[dim]For full Cognito management (create, teardown), install daylily-cognito:[/dim]"
106
+ )
100
107
  console.print("[dim] pip install daylily-cognito[/dim]")
101
108
 
102
109
  @app.command("create")
103
110
  def create():
104
111
  """Create/configure a Cognito user pool (requires daylily-cognito)."""
105
112
  console.print("[yellow]⚠[/yellow] This command requires daylily-cognito")
106
- console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
113
+ console.print(' Install with: [cyan]pip install -e ".[auth]"[/cyan]')
107
114
  raise typer.Exit(1)
108
115
 
109
116
  @app.command("teardown")
110
117
  def teardown():
111
118
  """Remove Cognito configuration (requires daylily-cognito)."""
112
119
  console.print("[yellow]⚠[/yellow] This command requires daylily-cognito")
113
- console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
120
+ console.print(' Install with: [cyan]pip install -e ".[auth]"[/cyan]')
114
121
  raise typer.Exit(1)
115
122
 
116
123
  return app
@@ -118,4 +125,3 @@ def _create_fallback_app() -> typer.Typer:
118
125
 
119
126
  # Export the cognito app - either the full version from daylily-cognito or the fallback
120
127
  cognito_app = _get_cognito_app()
121
-
zebra_day/cli/gui.py CHANGED
@@ -7,7 +7,6 @@ import sys
7
7
  import time
8
8
  from datetime import datetime
9
9
  from pathlib import Path
10
- from typing import Optional, Literal
11
10
 
12
11
  import typer
13
12
  from rich.console import Console
@@ -20,7 +19,11 @@ console = Console()
20
19
  # PID and log file locations
21
20
  STATE_DIR = xdg.get_state_dir()
22
21
  LOG_DIR = xdg.get_logs_dir()
22
+ CONFIG_DIR = xdg.get_config_dir()
23
23
  PID_FILE = STATE_DIR / "gui.pid"
24
+ DEFAULT_CERT_DIR = CONFIG_DIR / "certs"
25
+ DEFAULT_CERT_FILE = DEFAULT_CERT_DIR / "server.crt"
26
+ DEFAULT_KEY_FILE = DEFAULT_CERT_DIR / "server.key"
24
27
 
25
28
 
26
29
  def _ensure_dirs():
@@ -35,13 +38,13 @@ def _get_log_file() -> Path:
35
38
  return LOG_DIR / f"gui_{ts}.log"
36
39
 
37
40
 
38
- def _get_latest_log() -> Optional[Path]:
41
+ def _get_latest_log() -> Path | None:
39
42
  """Get the most recent log file."""
40
43
  logs = sorted(LOG_DIR.glob("gui_*.log"), reverse=True)
41
44
  return logs[0] if logs else None
42
45
 
43
46
 
44
- def _get_pid() -> Optional[int]:
47
+ def _get_pid() -> int | None:
45
48
  """Get the running server PID if exists."""
46
49
  if PID_FILE.exists():
47
50
  try:
@@ -57,20 +60,71 @@ def _check_auth_dependencies() -> bool:
57
60
  """Check if auth dependencies are available."""
58
61
  try:
59
62
  import jose # noqa: F401
63
+
60
64
  return True
61
65
  except ImportError:
62
66
  return False
63
67
 
64
68
 
69
+ def _resolve_ssl_paths(cert: str | None, key: str | None) -> tuple[str | None, str | None, bool]:
70
+ """
71
+ Resolve SSL certificate and key paths.
72
+
73
+ Priority:
74
+ 1. Explicit --cert/--key arguments
75
+ 2. SSL_CERT_PATH/SSL_KEY_PATH environment variables
76
+ 3. Default paths in ~/.config/zebra_day/certs/
77
+
78
+ Returns:
79
+ Tuple of (cert_path, key_path, use_https)
80
+ """
81
+ cert_path = cert
82
+ key_path = key
83
+
84
+ # Check environment variables
85
+ if not cert_path:
86
+ cert_path = os.environ.get("SSL_CERT_PATH")
87
+ if not key_path:
88
+ key_path = os.environ.get("SSL_KEY_PATH")
89
+
90
+ # Check default paths
91
+ if not cert_path and DEFAULT_CERT_FILE.exists():
92
+ cert_path = str(DEFAULT_CERT_FILE)
93
+ if not key_path and DEFAULT_KEY_FILE.exists():
94
+ key_path = str(DEFAULT_KEY_FILE)
95
+
96
+ # Validate both exist
97
+ if cert_path and key_path:
98
+ if Path(cert_path).exists() and Path(key_path).exists():
99
+ return cert_path, key_path, True
100
+
101
+ return None, None, False
102
+
103
+
65
104
  @gui_app.command("start")
66
105
  def start(
67
106
  port: int = typer.Option(8118, "--port", "-p", help="Port to run the server on"),
68
107
  host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind to"),
69
108
  auth: str = typer.Option("none", "--auth", "-a", help="Authentication mode: none or cognito"),
70
109
  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"),
110
+ background: bool = typer.Option(
111
+ True, "--background/--foreground", "-b/-f", help="Run in background"
112
+ ),
113
+ cert: str | None = typer.Option(None, "--cert", help="Path to SSL certificate file"),
114
+ key: str | None = typer.Option(None, "--key", help="Path to SSL private key file"),
115
+ no_https: bool = typer.Option(
116
+ False, "--no-https", help="Disable HTTPS even if certificates are available"
117
+ ),
72
118
  ):
73
- """Start the zebra_day web UI server."""
119
+ """Start the zebra_day web UI server.
120
+
121
+ By default, HTTPS is enabled if certificates are found in:
122
+ - Explicit --cert/--key arguments
123
+ - SSL_CERT_PATH/SSL_KEY_PATH environment variables
124
+ - ~/.config/zebra_day/certs/server.crt and server.key
125
+
126
+ Use --no-https to force HTTP mode.
127
+ """
74
128
  _ensure_dirs()
75
129
 
76
130
  # Validate auth option
@@ -89,7 +143,7 @@ def start(
89
143
  if auth == "cognito":
90
144
  if not _check_auth_dependencies():
91
145
  console.print("[red]✗[/red] Authentication requested but python-jose is not installed")
92
- console.print(" Install with: [cyan]pip install -e \".[auth]\"[/cyan]")
146
+ console.print(' Install with: [cyan]pip install -e ".[auth]"[/cyan]')
93
147
  raise typer.Exit(1)
94
148
 
95
149
  # Check required env vars
@@ -105,17 +159,47 @@ def start(
105
159
  raise typer.Exit(1)
106
160
  console.print("[green]✓[/green] Cognito authentication enabled")
107
161
 
108
- # Build command
162
+ # Resolve SSL paths
163
+ cert_path, key_path, use_https = _resolve_ssl_paths(cert, key)
164
+
165
+ if no_https:
166
+ use_https = False
167
+ cert_path = None
168
+ key_path = None
169
+
170
+ protocol = "https" if use_https else "http"
171
+
172
+ if use_https:
173
+ console.print("[green]✓[/green] HTTPS enabled")
174
+ console.print(f" Certificate: [dim]{cert_path}[/dim]")
175
+ console.print(f" Private key: [dim]{key_path}[/dim]")
176
+ else:
177
+ console.print("[yellow]⚠[/yellow] Running in HTTP mode (insecure)")
178
+ console.print(" For HTTPS, generate certificates with mkcert:")
179
+ console.print(f" [dim]mkdir -p {DEFAULT_CERT_DIR}[/dim]")
180
+ console.print(
181
+ f" [dim]mkcert -cert-file {DEFAULT_CERT_FILE} -key-file {DEFAULT_KEY_FILE} localhost 127.0.0.1 ::1[/dim]"
182
+ )
183
+
184
+ # Build command with SSL parameters
185
+ ssl_args = ""
186
+ if use_https and cert_path and key_path:
187
+ ssl_args = f", ssl_certfile='{cert_path}', ssl_keyfile='{key_path}'"
188
+
109
189
  cmd = [
110
190
  sys.executable,
111
191
  "-c",
112
- f"from zebra_day.web.app import run_server; run_server(host='{host}', port={port}, reload={reload}, auth='{auth}')",
192
+ f"from zebra_day.web.app import run_server; run_server(host='{host}', port={port}, reload={reload}, auth='{auth}'{ssl_args})",
113
193
  ]
114
194
 
115
195
  # Set up environment
116
196
  env = os.environ.copy()
117
197
  env["PYTHONUNBUFFERED"] = "1"
118
198
  env["ZEBRA_DAY_AUTH_MODE"] = auth
199
+ if cert_path:
200
+ env["SSL_CERT_PATH"] = cert_path
201
+ if key_path:
202
+ env["SSL_KEY_PATH"] = key_path
119
203
 
120
204
  if reload:
121
205
  background = False
@@ -149,10 +233,12 @@ def start(
149
233
 
150
234
  PID_FILE.write_text(str(proc.pid))
151
235
  console.print(f"[green]✓[/green] Server started (PID {proc.pid})")
152
- console.print(f" URL: [cyan]http://{host}:{port}[/cyan]")
236
+ console.print(f" URL: [cyan]{protocol}://{host}:{port}[/cyan]")
153
237
  console.print(f" Logs: [dim]{log_file}[/dim]")
154
238
  else:
155
- console.print(f"[green]✓[/green] Starting server on [cyan]http://{host}:{port}[/cyan]")
239
+ console.print(
240
+ f"[green]✓[/green] Starting server on [cyan]{protocol}://{host}:{port}[/cyan]"
241
+ )
156
242
  console.print(" Press Ctrl+C to stop\n")
157
243
  try:
158
244
  result = subprocess.run(cmd, cwd=Path.cwd(), env=env)
@@ -188,7 +274,7 @@ def stop():
188
274
  console.print("[yellow]⚠[/yellow] Server was not running")
189
275
  except PermissionError:
190
276
  console.print(f"[red]✗[/red] Permission denied stopping PID {pid}")
191
- raise typer.Exit(1)
277
+ raise typer.Exit(1) from None
192
278
 
193
279
 
194
280
  @gui_app.command("status")
@@ -197,8 +283,11 @@ def status():
197
283
  pid = _get_pid()
198
284
  if pid:
199
285
  log_file = _get_latest_log()
286
+ # Check if HTTPS is likely enabled based on cert availability
287
+ _, _, use_https = _resolve_ssl_paths(None, None)
288
+ protocol = "https" if use_https else "http"
200
289
  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]")
290
+ console.print(f" URL: [cyan]{protocol}://0.0.0.0:8118[/cyan]")
202
291
  if log_file:
203
292
  console.print(f" Logs: [dim]{log_file}[/dim]")
204
293
  else:
@@ -252,4 +341,3 @@ def restart(
252
341
  stop()
253
342
  time.sleep(1)
254
343
  start(port=port, host=host, auth=auth, reload=False, background=True)
255
-