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/zpl_renderer.py CHANGED
@@ -13,9 +13,9 @@ Supports the ZPL commands used in zebra_day templates:
13
13
  - ^BQN: QR code
14
14
  - ^BXN: Data Matrix
15
15
  """
16
+
16
17
  from __future__ import annotations
17
18
 
18
- import io
19
19
  import logging
20
20
  import re
21
21
  from dataclasses import dataclass, field
@@ -25,6 +25,7 @@ from PIL import Image, ImageDraw, ImageFont
25
25
 
26
26
  try:
27
27
  import zint
28
+
28
29
  ZINT_AVAILABLE = True
29
30
  except ImportError:
30
31
  ZINT_AVAILABLE = False
@@ -32,21 +33,22 @@ except ImportError:
32
33
  _log = logging.getLogger(__name__)
33
34
 
34
35
  # Default label dimensions for 4x6 inch label at 8 dpmm (203 dpi)
35
- DEFAULT_LABEL_WIDTH_DOTS = 812 # 4 inches * 203 dpi
36
+ DEFAULT_LABEL_WIDTH_DOTS = 812 # 4 inches * 203 dpi
36
37
  DEFAULT_LABEL_HEIGHT_DOTS = 1218 # 6 inches * 203 dpi
37
38
 
38
39
  # Barcode type mappings
39
40
  BARCODE_TYPES = {
40
- 'B3N': 'CODE39', # Code 39
41
- 'BCN': 'CODE128', # Code 128
42
- 'BQN': 'QRCODE', # QR Code
43
- 'BXN': 'DATAMATRIX', # Data Matrix
41
+ "B3N": "CODE39", # Code 39
42
+ "BCN": "CODE128", # Code 128
43
+ "BQN": "QRCODE", # QR Code
44
+ "BXN": "DATAMATRIX", # Data Matrix
44
45
  }
45
46
 
46
47
 
47
48
  @dataclass
48
49
  class FontSpec:
49
50
  """Font specification from ZPL ^A command."""
51
+
50
52
  height: int = 30
51
53
  width: int = 20
52
54
 
@@ -54,6 +56,7 @@ class FontSpec:
54
56
  @dataclass
55
57
  class BarcodeSpec:
56
58
  """Barcode specification from ZPL ^BY command."""
59
+
57
60
  module_width: int = 2
58
61
  ratio: float = 3.0
59
62
  height: int = 10
@@ -62,6 +65,7 @@ class BarcodeSpec:
62
65
  @dataclass
63
66
  class RenderState:
64
67
  """Current rendering state while parsing ZPL."""
68
+
65
69
  x: int = 0
66
70
  y: int = 0
67
71
  font: FontSpec = field(default_factory=FontSpec)
@@ -74,7 +78,7 @@ def _get_font(height: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
74
78
  """Get a font at the specified height. Falls back to default if unavailable."""
75
79
  try:
76
80
  # Try common monospace fonts
77
- for font_name in ['DejaVuSansMono.ttf', 'Menlo.ttc', 'Courier New.ttf', 'monospace']:
81
+ for font_name in ["DejaVuSansMono.ttf", "Menlo.ttc", "Courier New.ttf", "monospace"]:
78
82
  try:
79
83
  return ImageFont.truetype(font_name, height)
80
84
  except OSError:
@@ -85,26 +89,28 @@ def _get_font(height: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
85
89
  return ImageFont.load_default()
86
90
 
87
91
 
88
- def _render_barcode(barcode_type: str, data: str, height: int = 40, module_width: int = 2) -> Image.Image | None:
92
+ def _render_barcode(
93
+ barcode_type: str, data: str, height: int = 40, module_width: int = 2
94
+ ) -> Image.Image | None:
89
95
  """Render a barcode using zint-bindings."""
90
96
  if not ZINT_AVAILABLE:
91
97
  _log.warning("zint-bindings not available, cannot render barcode")
92
98
  return None
93
99
 
94
- import tempfile
95
100
  import os
101
+ import tempfile
96
102
 
97
103
  try:
98
104
  symbol = zint.Symbol()
99
105
 
100
106
  # Map barcode type to zint symbology
101
- if barcode_type == 'CODE39':
107
+ if barcode_type == "CODE39":
102
108
  symbol.symbology = zint.Symbology.CODE39
103
- elif barcode_type == 'CODE128':
109
+ elif barcode_type == "CODE128":
104
110
  symbol.symbology = zint.Symbology.CODE128
105
- elif barcode_type == 'QRCODE':
111
+ elif barcode_type == "QRCODE":
106
112
  symbol.symbology = zint.Symbology.QRCODE
107
- elif barcode_type == 'DATAMATRIX':
113
+ elif barcode_type == "DATAMATRIX":
108
114
  symbol.symbology = zint.Symbology.DATAMATRIX
109
115
  else:
110
116
  _log.warning("Unknown barcode type: %s", barcode_type)
@@ -115,7 +121,7 @@ def _render_barcode(barcode_type: str, data: str, height: int = 40, module_width
115
121
  symbol.show_text = False # ZPL typically handles text separately
116
122
 
117
123
  # Create temp file for output
118
- with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
124
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
119
125
  temp_path = f.name
120
126
 
121
127
  symbol.outfile = temp_path
@@ -124,7 +130,7 @@ def _render_barcode(barcode_type: str, data: str, height: int = 40, module_width
124
130
 
125
131
  # Load the image
126
132
  if os.path.exists(temp_path):
127
- img = Image.open(temp_path).convert('RGBA')
133
+ img = Image.open(temp_path).convert("RGBA")
128
134
  os.unlink(temp_path) # Clean up temp file
129
135
  return img
130
136
  return None
@@ -136,7 +142,7 @@ def _render_barcode(barcode_type: str, data: str, height: int = 40, module_width
136
142
  def _parse_font_command(cmd: str) -> FontSpec:
137
143
  """Parse ^A0N or ^ADN font command."""
138
144
  # ^A0N,height,width or ^ADN,height,width
139
- parts = cmd.split(',')
145
+ parts = cmd.split(",")
140
146
  height = int(parts[1]) if len(parts) > 1 and parts[1].strip() else 30
141
147
  width = int(parts[2]) if len(parts) > 2 and parts[2].strip() else height // 2
142
148
  return FontSpec(height=height, width=width)
@@ -145,7 +151,7 @@ def _parse_font_command(cmd: str) -> FontSpec:
145
151
  def _parse_barcode_default(cmd: str) -> BarcodeSpec:
146
152
  """Parse ^BY command for barcode defaults."""
147
153
  # ^BY[module_width],[ratio],[height]
148
- parts = cmd.split(',')
154
+ parts = cmd.split(",")
149
155
  module_width = int(parts[0]) if parts[0].strip() else 2
150
156
  ratio = float(parts[1]) if len(parts) > 1 and parts[1].strip() else 3.0
151
157
  height = int(parts[2]) if len(parts) > 2 and parts[2].strip() else 10
@@ -155,7 +161,7 @@ def _parse_barcode_default(cmd: str) -> BarcodeSpec:
155
161
  def _parse_position(cmd: str) -> tuple[int, int]:
156
162
  """Parse ^FO command for field origin."""
157
163
  # ^FOx,y
158
- parts = cmd.split(',')
164
+ parts = cmd.split(",")
159
165
  x = int(parts[0]) if parts[0].strip() else 0
160
166
  y = int(parts[1]) if len(parts) > 1 and parts[1].strip() else 0
161
167
  return x, y
@@ -164,7 +170,7 @@ def _parse_position(cmd: str) -> tuple[int, int]:
164
170
  def _parse_barcode_command(cmd: str) -> int:
165
171
  """Parse barcode command (^B3N, ^BCN, etc.) and return height."""
166
172
  # Format: ^B3N,orientation,height,... or ^BCN,orientation,height,...
167
- parts = cmd.split(',')
173
+ parts = cmd.split(",")
168
174
  # Height is usually the 3rd parameter (index 2) for most barcode commands
169
175
  if len(parts) > 2 and parts[2].strip():
170
176
  try:
@@ -196,37 +202,41 @@ def render_zpl_to_png(
196
202
  output_path.parent.mkdir(parents=True, exist_ok=True)
197
203
 
198
204
  # Create white background image
199
- img = Image.new('RGB', (width, height), 'white')
205
+ img = Image.new("RGB", (width, height), "white")
200
206
  draw = ImageDraw.Draw(img)
201
207
 
202
208
  state = RenderState()
203
209
 
204
210
  # Parse ZPL commands - split on ^ character
205
- commands = re.split(r'\^', zpl_string)
206
-
207
- pending_text: str | None = None
211
+ commands = re.split(r"\^", zpl_string)
208
212
 
209
213
  for cmd in commands:
210
214
  cmd = cmd.strip()
211
215
  if not cmd:
212
216
  continue
213
217
 
218
+ # Strip inline comments (ZPL uses ; for comments)
219
+ if ";" in cmd:
220
+ cmd = cmd.split(";")[0].strip()
221
+ if not cmd:
222
+ continue
223
+
214
224
  # Label start/end - ignore
215
- if cmd.startswith('XA') or cmd.startswith('XZ'):
225
+ if cmd.startswith("XA") or cmd.startswith("XZ"):
216
226
  continue
217
227
 
218
228
  # Field origin - set position
219
- if cmd.startswith('FO'):
229
+ if cmd.startswith("FO"):
220
230
  state.x, state.y = _parse_position(cmd[2:])
221
231
  continue
222
232
 
223
233
  # Font commands
224
- if cmd.startswith('A0N') or cmd.startswith('ADN'):
234
+ if cmd.startswith("A0N") or cmd.startswith("ADN"):
225
235
  state.font = _parse_font_command(cmd)
226
236
  continue
227
237
 
228
238
  # Barcode default
229
- if cmd.startswith('BY'):
239
+ if cmd.startswith("BY"):
230
240
  state.barcode = _parse_barcode_default(cmd[2:])
231
241
  continue
232
242
 
@@ -238,10 +248,10 @@ def render_zpl_to_png(
238
248
  break
239
249
  else:
240
250
  # Not a barcode command, check for field data
241
- if cmd.startswith('FD'):
251
+ if cmd.startswith("FD"):
242
252
  # Extract data between FD and FS
243
253
  data = cmd[2:]
244
- if data.endswith('FS'):
254
+ if data.endswith("FS"):
245
255
  data = data[:-2]
246
256
 
247
257
  # If there's a pending barcode type, render barcode
@@ -253,21 +263,22 @@ def render_zpl_to_png(
253
263
  module_width=state.barcode.module_width,
254
264
  )
255
265
  if bc_img:
256
- img.paste(bc_img, (state.x, state.y), bc_img if bc_img.mode == 'RGBA' else None)
266
+ img.paste(
267
+ bc_img, (state.x, state.y), bc_img if bc_img.mode == "RGBA" else None
268
+ )
257
269
  state.current_barcode_type = None
258
270
  else:
259
271
  # Render text
260
272
  font = _get_font(state.font.height)
261
- draw.text((state.x, state.y), data, fill='black', font=font)
273
+ draw.text((state.x, state.y), data, fill="black", font=font)
262
274
  continue
263
275
 
264
276
  # Field separator - just a marker, usually handled with FD
265
- if cmd.startswith('FS'):
277
+ if cmd.startswith("FS"):
266
278
  continue
267
279
 
268
280
  # Save the image
269
- img.save(str(output_path), 'PNG')
281
+ img.save(str(output_path), "PNG")
270
282
  _log.info("Label image saved as %s", output_path)
271
283
 
272
284
  return str(output_path)
273
-
@@ -1,9 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zebra_day
3
- Version: 1.0.2
3
+ Version: 2.1.4
4
4
  Summary: A Python library to manage a Zebra printer fleet and an API for ZPL print requests.
5
- Home-page: https://github.com/Daylily-Informatics/zebra_day
6
- Author: John Major
7
5
  Author-email: John Major <john@daylilyinformatics.com>
8
6
  License: MIT
9
7
  Project-URL: Homepage, https://github.com/Daylily-Informatics/zebra_day
@@ -37,12 +35,13 @@ Requires-Dist: typer>=0.9.0
37
35
  Requires-Dist: rich>=13.0.0
38
36
  Requires-Dist: pillow>=10.0.0
39
37
  Requires-Dist: zint-bindings>=1.2.0
38
+ Requires-Dist: httpx
40
39
  Provides-Extra: dev
41
40
  Requires-Dist: pytest>=7.4.0; extra == "dev"
42
41
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
43
42
  Requires-Dist: ipython>=8.16.0; extra == "dev"
43
+ Requires-Dist: playwright>=1.40.0; extra == "dev"
44
44
  Provides-Extra: lint
45
- Requires-Dist: black>=23.0.0; extra == "lint"
46
45
  Requires-Dist: ruff>=0.1.0; extra == "lint"
47
46
  Requires-Dist: mypy>=1.0.0; extra == "lint"
48
47
  Provides-Extra: docs
@@ -54,13 +53,17 @@ Requires-Dist: python-jose[cryptography]>=3.3.0; extra == "auth"
54
53
  Requires-Dist: boto3>=1.26.0; extra == "auth"
55
54
  Provides-Extra: all
56
55
  Requires-Dist: zebra_day[auth,dev,docs,lint]; extra == "all"
57
- Dynamic: author
58
- Dynamic: home-page
59
56
  Dynamic: license-file
60
57
 
61
58
  <img src=zebra_day/imgs/bar_red.png>
62
59
 
63
- ## zebra_day Overview [1.0.2](https://github.com/Daylily-Informatics/zebra_day/releases/tag/1.0.2)
60
+ ## zebra_day
61
+
62
+ [![GitHub Release](https://img.shields.io/github/v/release/Daylily-Informatics/zebra_day?style=flat-square&label=release)](https://github.com/Daylily-Informatics/zebra_day/releases/latest)
63
+ [![GitHub Tag](https://img.shields.io/github/v/tag/Daylily-Informatics/zebra_day?style=flat-square&label=tag)](https://github.com/Daylily-Informatics/zebra_day/tags)
64
+ [![PyPI](https://img.shields.io/pypi/v/zebra_day?style=flat-square)](https://pypi.org/project/zebra_day/)
65
+ [![Python CI](https://github.com/Daylily-Informatics/zebra_day/actions/workflows/main.yaml/badge.svg)](https://github.com/Daylily-Informatics/zebra_day/actions/workflows/main.yaml)
66
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
64
67
 
65
68
  ### Build, Deploy, Run, Monitor, Teardown
66
69
 
@@ -76,23 +79,23 @@ pytest -v
76
79
 
77
80
  # Run Linting (requires pip install -e ".[lint]")
78
81
  ruff check zebra_day tests
79
- black --check zebra_day tests
82
+ ruff format --check zebra_day tests
80
83
  mypy zebra_day --ignore-missing-imports
81
84
 
82
- # CLI Commands (new in 0.6.0)
85
+ # CLI Commands
83
86
  zday --help # Show all commands
84
87
  zday bootstrap # First-time setup: scan for printers
85
88
  zday gui start # Start web UI in background
86
89
  zday gui stop # Stop web UI
87
90
  zday gui status # Check if web UI is running
88
91
 
89
- # Health Checks
90
- curl http://localhost:8118/healthz # Basic health check
91
- curl http://localhost:8118/readyz # Readiness check (printer mgr initialized)
92
+ # Health Checks (use https:// if certificates are configured, http:// otherwise)
93
+ curl https://localhost:8118/healthz # Basic health check
94
+ curl https://localhost:8118/readyz # Readiness check (printer mgr initialized)
92
95
 
93
96
  # API Documentation
94
- # Visit http://localhost:8118/docs for interactive OpenAPI docs
95
- # Visit http://localhost:8118/redoc for alternative API documentation
97
+ # Visit https://localhost:8118/docs for interactive OpenAPI docs
98
+ # Visit https://localhost:8118/redoc for alternative API documentation
96
99
  ```
97
100
 
98
101
  <ul>
@@ -153,19 +156,20 @@ zday gui stop
153
156
 
154
157
  > <a href=zebra_day/docs/zebra_day_ui_guide.md >ui capabilities full details</a>
155
158
 
156
- #### Some UI Niceties
157
- ##### Zebra Printer Fleet Dashboard
158
- <img width="400" alt="fleetreport" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/8a66bc11-f8f5-4c40-9970-36d554a4593a">
159
+ #### Modern Web UI
160
+
161
+ ##### Dashboard
162
+ <img width="800" alt="dashboard" src="zebra_day/imgs/ui_dashboard.png">
159
163
 
160
- ##### Zebra Printer, Single Printer Detail View
161
- <img width="690" alt="Screenshot 2023-11-01 at 1 35 36 AM" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/7438df35-9e92-474e-a2ef-57d3c3ee23d7">
164
+ ##### Printer Fleet
165
+ <img width="800" alt="printers" src="zebra_day/imgs/ui_printers.png">
162
166
 
163
- ##### ZPL Label Editing IRT
164
- <img width="345" alt="zpl_editing" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/15aac332-c5f8-4ce6-be6c-9c403fd8d35d">
167
+ ##### ZPL Template Editor
168
+ <img width="800" alt="templates" src="zebra_day/imgs/ui_templates.png">
165
169
 
166
170
  </ul>
167
171
 
168
- ### CLI Reference (0.6.0+)
172
+ ### CLI Reference
169
173
 
170
174
  The `zday` CLI provides a comprehensive interface for managing your Zebra printer fleet.
171
175
 
@@ -181,7 +185,7 @@ zday status # Show printer fleet status, service health
181
185
  zday bootstrap # First-time setup: scan network, initialize config
182
186
 
183
187
  # GUI server management
184
- zday gui start [--auth none|cognito] [--host HOST] [--port PORT]
188
+ zday gui start [--auth none|cognito] [--host HOST] [--port PORT] [--cert FILE] [--key FILE] [--no-https]
185
189
  zday gui stop
186
190
  zday gui status
187
191
  zday gui logs [--tail N] [--follow]
@@ -213,6 +217,79 @@ The old commands `zday_start` and `zday_quickstart` still work but are deprecate
213
217
  | `zday_start` | `zday gui start` |
214
218
  | `zday_start --auth cognito` | `zday gui start --auth cognito` |
215
219
 
220
+ ### Local HTTPS Setup
221
+
222
+ The zebra_day web UI supports HTTPS for secure local development. By default, HTTPS is enabled if certificates are found.
223
+
224
+ > **Note**: The `zday bootstrap` command automatically generates certificates if mkcert is installed and the CA is configured.
225
+ > Manual certificate generation (below) is only needed if you skip bootstrap or want custom hostnames.
226
+
227
+ #### One-Time Setup (mkcert)
228
+
229
+ ```bash
230
+ # Install mkcert
231
+ # macOS
232
+ brew install mkcert
233
+
234
+ # Ubuntu/Debian
235
+ sudo apt install mkcert
236
+
237
+ # Create and install local CA (one-time, requires password)
238
+ mkcert -install
239
+ ```
240
+
241
+ #### Automatic Certificate Generation (Recommended)
242
+
243
+ After installing mkcert and running `mkcert -install`, the bootstrap command will automatically generate certificates:
244
+
245
+ ```bash
246
+ zday bootstrap
247
+ # Output includes:
248
+ # ✓ Certificates generated: ~/.config/zebra_day/certs/server.crt
249
+ ```
250
+
251
+ #### Manual Certificate Generation (Alternative)
252
+
253
+ If you need to manually generate certificates (e.g., for custom hostnames):
254
+
255
+ ```bash
256
+ # Create certificate directory
257
+ mkdir -p ~/.config/zebra_day/certs
258
+
259
+ # Generate locally-trusted certificates
260
+ mkcert -cert-file ~/.config/zebra_day/certs/server.crt \
261
+ -key-file ~/.config/zebra_day/certs/server.key \
262
+ localhost 127.0.0.1 ::1
263
+ ```
264
+
265
+ #### HTTPS Options
266
+
267
+ | Method | Description |
268
+ |--------|-------------|
269
+ | **Auto-detect** | If certs exist in `~/.config/zebra_day/certs/`, HTTPS is enabled automatically |
270
+ | **Explicit paths** | `zday gui start --cert /path/to/cert.crt --key /path/to/key.key` |
271
+ | **Environment vars** | Set `SSL_CERT_PATH` and `SSL_KEY_PATH` |
272
+ | **Force HTTP** | `zday gui start --no-https` |
273
+
274
+ #### Verify HTTPS
275
+
276
+ ```bash
277
+ # Start server (will use HTTPS if certs are found)
278
+ zday gui start
279
+
280
+ # Test connection
281
+ curl https://localhost:8118/healthz
282
+ ```
283
+
284
+ #### Troubleshooting
285
+
286
+ | Issue | Solution |
287
+ |-------|----------|
288
+ | Browser shows "Not Secure" | Run `mkcert -install` to trust the local CA |
289
+ | Certificate not found | Check paths: `ls ~/.config/zebra_day/certs/` |
290
+ | Permission denied | Ensure cert files are readable: `chmod 644 ~/.config/zebra_day/certs/*` |
291
+ | Want HTTP only | Use `--no-https` flag: `zday gui start --no-https` |
292
+
216
293
  ### It Is 3+ Things
217
294
 
218
295
  (1) Zebra Printer Management & Configuration
@@ -246,7 +323,7 @@ The old commands `zday_start` and `zday_quickstart` still work but are deprecate
246
323
  <ul>
247
324
  <ul>
248
325
 
249
- > <img src=zebra_day/imgs/UBC_gantt_chart.png height=200 width=450>
326
+ > <img src=zebra_day/imgs/legacy/UBC_gantt_chart.png height=200 width=450>
250
327
 
251
328
  </ul>
252
329
  </ul>
@@ -304,7 +381,7 @@ python -m build # Creates dist/*.whl and dist/*.tar.gz
304
381
 
305
382
  ### File Locations (XDG-compliant)
306
383
 
307
- zebra_day 0.6.0+ uses XDG Base Directory specification for file storage:
384
+ zebra_day uses XDG Base Directory specification for file storage:
308
385
 
309
386
  | Type | macOS | Linux |
310
387
  |------|-------|-------|
@@ -386,11 +463,10 @@ Run 'zday gui start' to launch the web interface.
386
463
  $ zday gui start
387
464
 
388
465
  Starting zebra_day web server...
389
- Server running at: http://192.168.1.12:8118
466
+ Server running at: https://192.168.1.12:8118
390
467
 
391
- Modern UI: http://192.168.1.12:8118/
392
- Legacy UI: http://192.168.1.12:8118/legacy
393
- API Docs: http://192.168.1.12:8118/docs
468
+ Dashboard: https://192.168.1.12:8118/
469
+ API Docs: https://192.168.1.12:8118/docs
394
470
 
395
471
  Server started in background (PID: 12345)
396
472
  Use 'zday gui status' to check status
@@ -401,26 +477,23 @@ Use 'zday gui stop' to stop the server
401
477
 
402
478
  #### zebra_day Web GUI
403
479
 
404
- ##### Home Page
405
- <img src=zebra_day/imgs/zday_quick_gui.png>
480
+ ##### Dashboard
481
+ <img width="800" alt="dashboard" src="zebra_day/imgs/ui_dashboard.png">
406
482
 
407
- ##### Zebra Fleet Auto Discovery & Status Report
483
+ ##### Printer Fleet
484
+ <img width="800" alt="printers" src="zebra_day/imgs/ui_printers.png">
408
485
 
409
- <img width="1024" alt="fleetreport" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/8a66bc11-f8f5-4c40-9970-36d554a4593a">
486
+ ##### Print Request
487
+ <img width="800" alt="print_request" src="zebra_day/imgs/ui_print_request.png">
410
488
 
489
+ ##### ZPL Template Editor
490
+ <img width="800" alt="templates" src="zebra_day/imgs/ui_templates.png">
411
491
 
412
- ##### Zebra Printer Fleet Config Json Editing
413
- <img width="472" alt="pconfjson" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/0813cb07-4c5a-4cc9-9b33-d00e8424385e">
414
- One printer configured.
415
-
416
-
417
- ##### ZPL Template Drafting / Preview PNG / Test Print / Save
418
- <img width="953" alt="zpl_editing" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/15aac332-c5f8-4ce6-be6c-9c403fd8d35d">
419
-
420
-
421
- ##### Manual Print Requests
422
- <img width="895" alt="printmanual" src="https://github.com/Daylily-Informatics/zebra_day/assets/4713659/72442f68-984f-4264-93ec-9878372d26f2">
492
+ ##### Configuration
493
+ <img width="800" alt="config" src="zebra_day/imgs/ui_config.png">
423
494
 
495
+ ##### API Documentation
496
+ <img width="800" alt="api_docs" src="zebra_day/imgs/ui_api_docs.png">
424
497
 
425
498
  <br><br>
426
499
 
@@ -436,21 +509,20 @@ zlab = zdpm.zpl()
436
509
 
437
510
  zlab.probe_zebra_printers_add_to_printers_json('192.168.1') # REPLACE the IP stub with the correct value for your network. This may take a few min to run. !! This command is not required if you've sucessuflly run the quickstart already, also, won't hurt.
438
511
 
439
- print(zlab.printers) # This should print out the json dict of all detected zebra printers. An empty dict, {}, is a failure of autodetection, and manual creation of the json file may be needed. If successful, the lab name assigned is 'scan-results', this may be edited latter.
512
+ print(zlab.printers) # This should print out the json dict of all detected zebra printers. An empty dict, {}, is a failure of autodetection, and manual creation of the json file may be needed. If successful, the lab name assigned is 'default', this may be edited later.
440
513
 
441
- # The json will loook something like this
442
- ## {'labs': {'scan-results': {'192.168.1.7': {'ip_address': '192.168.1.7', 'label_zpl_styles': ['test_2inX1in'], 'print_method': 'unk'}}}
443
- ## 'lab' name 'printer' name(can be edited latter) label_zpl_style
514
+ # The json will look something like this (v2.0.0 schema with nested printers)
515
+ ## {'schema_version': '2.0.0', 'labs': {'default': {'lab_name': 'Default', 'printers': {'192.168.1.7': {'ip_address': '192.168.1.7', ...}}}}}
444
516
 
445
- # Assuming a printer was detected, send a test print request. Using the 'lab', 'printer' and 'label_zpl_style' above (you'd have your own IP/Name, other values should remain the same for now. There are multiple label ZPL formats available, the test_2inX1in is for quick testing & only formats in the two UID values specified.
517
+ # Assuming a printer was detected, send a test print request. Using the 'lab', 'printer' and 'label_zpl_style' above (you'd have your own IP/Name, other values should remain the same for now. There are multiple label ZPL formats available, the test_2inX1in is for quick testing & only formats in the two UID values specified.
446
518
 
447
- zlab.print_zpl(lab='scan-results', printer_name='192.168.1.7', label_zpl_style='test_2inX1in', uid_barcode="123aUID")
519
+ zlab.print_zpl(lab='default', printer_name='192.168.1.7', label_zpl_style='test_2inX1in', uid_barcode="123aUID")
448
520
  # ZPL code sent successfully to the printer!
449
521
  # Out[13]: '^XA\n^FO235,20\n^BY1\n^B3N,N,40,N,N\n^FD123aUID^FS\n^FO235,70\n^ADN,30,20\n^FD123aUID^FS\n^FO235,115\n^ADN,25,12\n^FDalt_a^FS\n^FO235,145\n^ADN,25,12\n^FDalt_b^FS\n^FO70,180\n^FO235,170\n^ADN,30,20\n^FDalt_c^FS\n^FO490,180\n^ADN,25,12\n^FDalt_d^FS\n^XZ'
450
522
  ```
451
523
 
452
524
  * This will produce a label which looks like this (modulo printer config items needing attention).
453
- ![test_lab](zebra_day/imgs/quick_start_test_label2.png)
525
+ ![test_lab](zebra_day/imgs/legacy/quick_start_test_label2.png)
454
526
 
455
527
 
456
528
  ### [Programatic Guide](zebra_day/docs/programatic_guide.md)
@@ -496,27 +568,33 @@ zday gui start --foreground
496
568
  uvicorn zebra_day.web.app:create_app --host 0.0.0.0 --port 8118 --factory
497
569
  ```
498
570
 
499
- ### Modern vs Legacy UI
571
+ ### Web UI
500
572
 
501
- zebra_day 0.6.0+ includes a redesigned modern UI alongside the preserved legacy interface:
573
+ zebra_day 2.0.0+ features a modern, responsive web interface:
502
574
 
503
575
  | Interface | URL | Description |
504
576
  |-----------|-----|-------------|
505
- | **Modern UI** | `http://localhost:8118/` | New dashboard with stats, quick actions, improved navigation |
506
- | **Legacy UI** | `http://localhost:8118/legacy` | Original interface, fully functional |
507
- | **API Docs** | `http://localhost:8118/docs` | Interactive OpenAPI/Swagger documentation |
508
- | **ReDoc** | `http://localhost:8118/redoc` | Alternative API documentation |
577
+ | **Dashboard** | `https://localhost:8118/` | Printer fleet stats, quick actions, navigation |
578
+ | **Printers** | `https://localhost:8118/printers` | Printer status and management |
579
+ | **Print** | `https://localhost:8118/print` | Send print requests |
580
+ | **Templates** | `https://localhost:8118/templates` | ZPL template editor with live preview |
581
+ | **Config** | `https://localhost:8118/config` | Printer configuration management |
582
+ | **API Docs** | `https://localhost:8118/docs` | Interactive OpenAPI/Swagger documentation |
583
+ | **ReDoc** | `https://localhost:8118/redoc` | Alternative API documentation |
584
+
585
+ > **Note:** Use `http://` instead of `https://` if running without certificates (`--no-https`).
509
586
 
510
- Both interfaces provide full functionality. The modern UI offers:
587
+ The modern UI offers:
511
588
  - Dashboard with printer fleet statistics
512
589
  - Streamlined navigation
513
- - Improved template editor
590
+ - Improved template editor with live PNG preview
514
591
  - Better mobile responsiveness
592
+ - Configuration editing with backup management
515
593
 
516
594
  ### Print via HTTP API
517
595
 
518
596
  ```http
519
- http://YOUR.HOST.IP.ADDR:8118/_print_label?lab=scan-results&printer=192.168.1.7&label_zpl_style=test_2inX1in&uid_barcode=123aUID
597
+ http://YOUR.HOST.IP.ADDR:8118/_print_label?lab=default&printer=192.168.1.7&label_zpl_style=test_2inX1in&uid_barcode=123aUID
520
598
  ```
521
599
 
522
600
  * See the [Web UI Guide](zebra_day/docs/zebra_day_ui_guide.md) for full details.
@@ -579,7 +657,7 @@ When `--auth cognito` is enabled:
579
657
 
580
658
  Include the JWT token in the Authorization header:
581
659
  ```bash
582
- curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:8118/api/v1/printers
660
+ curl -H "Authorization: Bearer YOUR_JWT_TOKEN" https://localhost:8118/api/v1/printers
583
661
  ```
584
662
 
585
663
  ### Secrets
@@ -659,23 +737,23 @@ GET /favicon.ico 200 OK ~
659
737
  </pre>
660
738
 
661
739
  And looks like:
662
- <img src=zebra_day/imgs/ngrok.png>
740
+ <img src=zebra_day/imgs/legacy/ngrok.png>
663
741
 
664
742
  * If you leave the ngrok tunnel running, go to a different network, you can use the link named in the `Forwarding` row above to access the zebra_day UI, in the above example, this url would be `https://dfbf-23-93-175-197.ngrok-free.app`.
665
743
 
666
744
  #### Sending Label Print Requests
667
745
  ##### from a web browser on a different network
668
746
 
669
- `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&alt_a=altTEXTAA&alt_b=altTEXTBB&alt_c=altTEXTCC&alt_d=&alt_e=&alt_f=&lab=scan-results&printer=192.168.1.20&printer_ip=192.168.1.20&label_zpl_style=tube_2inX1in`
747
+ `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&alt_a=altTEXTAA&alt_b=altTEXTBB&alt_c=altTEXTCC&alt_d=&alt_e=&alt_f=&lab=default&printer=192.168.1.20&printer_ip=192.168.1.20&label_zpl_style=tube_2inX1in`
670
748
 
671
749
  ##### Using wget from a shell on a machine outside your local network
672
750
  ```bash
673
- wget "https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&alt_a=altTEXTAA&alt_b=altTEXTBB&alt_c=altTEXTCC&alt_d=&alt_e=&alt_f=&lab=scan-results&printer=192.168.1.20&printer_ip=192.168.1.20&label_zpl_style=tube_2inX1in"
751
+ wget "https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&alt_a=altTEXTAA&alt_b=altTEXTBB&alt_c=altTEXTCC&alt_d=&alt_e=&alt_f=&lab=default&printer=192.168.1.20&printer_ip=192.168.1.20&label_zpl_style=tube_2inX1in"
674
752
  ```
675
753
 
676
754
  ### From SalesForce
677
755
 
678
- * There are several ways to do this, but they all boil down to somehow formulating a URL for each print request, ie: `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&lab=scan-results&printer=192.168.1.20&label_zpl_style=tube_2inX1in`, and hitting the URL via Apex, Flow, etc.
756
+ * There are several ways to do this, but they all boil down to somehow formulating a URL for each print request, ie: `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode=UID33344455&lab=default&printer=192.168.1.20&label_zpl_style=tube_2inX1in`, and hitting the URL via Apex, Flow, etc.
679
757
  * To send a print request, you will need to know the API url, and the `lab`, `printer_name`, and `label_zpl_style` you wish to print the salesforce `Name` aka `UID` as a label. This example explains how to pass just one variable to print from salesforce, adding additional metadata to print involves adding additional params to the url being constructed.
680
758
 
681
759
 
@@ -730,7 +808,7 @@ Next, create a flow which uses this Apex Class.
730
808
  * Choose `Actions and Related Records`, and check the box at the bottom of the page to `Include a Run Asynchronously path...`
731
809
  * upon clicking this box, the graphic representation of the flow to the left of the page will now have 2 branches at the bottom of the flow rule, one `Run Immediately` and one `Run Asynchronously`. The `Run Immediately` branch was throwing errors, so I removed it to debug at a latter date.
732
810
  * Click the node just below the `Run Asynch` oval. Add an `Action`. Select the `Make HTTP Request` we created via the Apex Class above. Give it a `Label`, let the API Name auto generate.
733
- * In the `Endpoint URL` field, enter the url `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode={!$Record.Name}&lab=scan-results&printer=!!YOURPRINTERIP!!&label_zpl_style=tube_2inX1in`, where Record.Name will be replaced with the Object.Name from the object triggering the flow. Replace !!YOURPRINTERIP!! with one of the printer IPs zebra_day detected above. If you are using the auto-generated zebra printers config json file, you may leave `scan-results` as the value for `lab=` as this will be the default name given when zebra_day autodetects printers.
811
+ * In the `Endpoint URL` field, enter the url `https://dfbf-23-93-175-197.ngrok-free.app/_print_label?uid_barcode={!$Record.Name}&lab=default&printer=!!YOURPRINTERIP!!&label_zpl_style=tube_2inX1in`, where Record.Name will be replaced with the Object.Name from the object triggering the flow. Replace !!YOURPRINTERIP!! with one of the printer IPs zebra_day detected above. If you are using the auto-generated zebra printers config json file, you may leave `default` as the value for `lab=` as this will be the default name given when zebra_day autodetects printers.
734
812
  * add the same HTTPrequest action to the node just below `Run Immediately`.
735
813
  * click `Save` in the upper right corner of the page. Give it a name
736
814
  * Click `Debug Again`, run the `Run Immediately` branch first. This will fail.
@@ -775,12 +853,4 @@ Tthen run `sudo docker compose up --build -d` to run it then reach it at http://
775
853
 
776
854
  * Set varios printer config via ZPL commands (presently this package only fetches config).
777
855
 
778
-
779
- # BadgeLand
780
-
781
- [![Python CI](https://github.com/Daylily-Informatics/zebra_day/actions/workflows/main.yaml/badge.svg)](https://github.com/Daylily-Informatics/zebra_day/actions/workflows/main.yaml)
782
-
783
- <br>
784
-
785
-
786
856