zebra-day 0.0.37__py3-none-any.whl → 2.0.0__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 (173) hide show
  1. zebra_day/__init__.py +35 -0
  2. zebra_day/bin/__init__.py +0 -0
  3. zebra_day/cli/__init__.py +240 -0
  4. zebra_day/cli/cognito.py +121 -0
  5. zebra_day/cli/gui.py +338 -0
  6. zebra_day/cli/printer.py +168 -0
  7. zebra_day/cli/template.py +176 -0
  8. zebra_day/cmd_mgr.py +35 -0
  9. zebra_day/etc/Monoid-Regular-HalfTight-Dollar-0-1-l.ttf +0 -0
  10. zebra_day/etc/label_styles/blank.zpl +0 -0
  11. zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
  12. zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
  13. zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
  14. zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
  15. zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
  16. zebra_day/etc/label_styles/generic_2inX1in.zpl +21 -0
  17. zebra_day/etc/label_styles/plate_1inX0.25in.zpl +9 -0
  18. zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
  19. zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
  20. zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
  21. zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
  22. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
  23. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
  24. zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
  25. zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
  26. zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
  27. zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
  28. zebra_day/etc/label_styles/tmps/.hold +0 -0
  29. zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
  30. zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
  31. zebra_day/etc/label_styles/tube_2inX0.3in.zpl +15 -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_2inX1in.zpl +25 -0
  35. zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
  36. zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
  37. zebra_day/etc/old_printer_config/.hold +0 -0
  38. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
  39. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
  40. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
  41. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
  42. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
  43. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
  44. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
  45. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
  46. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
  47. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
  48. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
  49. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
  50. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
  51. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
  52. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
  53. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
  54. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
  55. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
  56. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.739070_printer_config.json +16 -0
  57. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
  58. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
  59. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
  60. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
  61. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
  62. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
  63. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
  64. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
  65. zebra_day/etc/printer_config.json +4 -0
  66. zebra_day/etc/printer_config.template.json +24 -0
  67. zebra_day/etc/tmp_printers0.json +5 -0
  68. zebra_day/etc/tmp_printers120.json +10 -0
  69. zebra_day/etc/tmp_printers145.json +10 -0
  70. zebra_day/etc/tmp_printers207.json +10 -0
  71. zebra_day/etc/tmp_printers374.json +5 -0
  72. zebra_day/etc/tmp_printers383.json +5 -0
  73. zebra_day/etc/tmp_printers450.json +5 -0
  74. zebra_day/etc/tmp_printers469.json +10 -0
  75. zebra_day/etc/tmp_printers485.json +10 -0
  76. zebra_day/etc/tmp_printers504.json +5 -0
  77. zebra_day/etc/tmp_printers531.json +10 -0
  78. zebra_day/etc/tmp_printers540.json +10 -0
  79. zebra_day/etc/tmp_printers542.json +10 -0
  80. zebra_day/etc/tmp_printers552.json +10 -0
  81. zebra_day/etc/tmp_printers608.json +5 -0
  82. zebra_day/etc/tmp_printers657.json +5 -0
  83. zebra_day/etc/tmp_printers715.json +10 -0
  84. zebra_day/etc/tmp_printers838.json +5 -0
  85. zebra_day/etc/tmp_printers839.json +5 -0
  86. zebra_day/etc/tmp_printers933.json +5 -0
  87. zebra_day/etc/tmp_printers957.json +5 -0
  88. zebra_day/etc/tmp_printers972.json +10 -0
  89. zebra_day/exceptions.py +88 -0
  90. zebra_day/files/.hold +0 -0
  91. zebra_day/files/blank_preview.png +0 -0
  92. zebra_day/files/corners_20cmX30cm_preview.png +0 -0
  93. zebra_day/files/generic_2inX1in_preview.png +0 -0
  94. zebra_day/files/hold +0 -0
  95. zebra_day/files/test_png_12020.png +0 -0
  96. zebra_day/files/test_png_12352.png +0 -0
  97. zebra_day/files/test_png_15472.png +0 -0
  98. zebra_day/files/test_png_17696.png +0 -0
  99. zebra_day/files/test_png_23477.png +0 -0
  100. zebra_day/files/test_png_24493.png +0 -0
  101. zebra_day/files/test_png_28157.png +0 -0
  102. zebra_day/files/test_png_30069.png +0 -0
  103. zebra_day/files/test_png_35832.png +0 -0
  104. zebra_day/files/test_png_36400.png +0 -0
  105. zebra_day/files/test_png_40816.png +0 -0
  106. zebra_day/files/test_png_47791.png +0 -0
  107. zebra_day/files/test_png_47799.png +0 -0
  108. zebra_day/files/test_png_49564.png +0 -0
  109. zebra_day/files/test_png_53848.png +0 -0
  110. zebra_day/files/test_png_55588.png +0 -0
  111. zebra_day/files/test_png_58809.png +0 -0
  112. zebra_day/files/test_png_62542.png +0 -0
  113. zebra_day/files/test_png_67242.png +0 -0
  114. zebra_day/files/test_png_89893.png +0 -0
  115. zebra_day/files/test_png_91597.png +0 -0
  116. zebra_day/files/test_png_93633.png +0 -0
  117. zebra_day/files/tmpbjo3k7q1.png +0 -0
  118. zebra_day/files/tmpigtr4pwy.png +0 -0
  119. zebra_day/files/tube_20mmX30mmA_preview.png +0 -0
  120. zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
  121. zebra_day/logging_config.py +74 -0
  122. zebra_day/logs/.hold +0 -0
  123. zebra_day/logs/print_requests.log +2 -0
  124. zebra_day/paths.py +143 -0
  125. zebra_day/print_mgr.py +557 -117
  126. zebra_day/static/datschund.css +140 -0
  127. zebra_day/static/datschund.png +0 -0
  128. zebra_day/static/daylily.png +0 -0
  129. zebra_day/static/favicon.svg +20 -0
  130. zebra_day/static/general.css +99 -0
  131. zebra_day/static/js/zebra_modern.js +172 -0
  132. zebra_day/static/lsmc.css +354 -0
  133. zebra_day/static/moon.jpeg +0 -0
  134. zebra_day/static/oakland.css +197 -0
  135. zebra_day/static/petrichor.css +150 -0
  136. zebra_day/static/popday_daylily.css +140 -0
  137. zebra_day/static/style.css +183 -0
  138. zebra_day/static/triangles.css +122 -0
  139. zebra_day/static/tron.css +277 -0
  140. zebra_day/static/zebra_modern.css +771 -0
  141. zebra_day/static/zebras.css +176 -0
  142. zebra_day/templates/modern/base.html +98 -0
  143. zebra_day/templates/modern/config.html +141 -0
  144. zebra_day/templates/modern/config_backups.html +59 -0
  145. zebra_day/templates/modern/config_editor.html +95 -0
  146. zebra_day/templates/modern/config_new.html +93 -0
  147. zebra_day/templates/modern/dashboard.html +160 -0
  148. zebra_day/templates/modern/print_request.html +145 -0
  149. zebra_day/templates/modern/print_result.html +88 -0
  150. zebra_day/templates/modern/printer_detail.html +244 -0
  151. zebra_day/templates/modern/printers.html +144 -0
  152. zebra_day/templates/modern/save_result.html +46 -0
  153. zebra_day/templates/modern/template_editor.html +175 -0
  154. zebra_day/templates/modern/templates.html +122 -0
  155. zebra_day/web/__init__.py +9 -0
  156. zebra_day/web/app.py +248 -0
  157. zebra_day/web/auth.py +172 -0
  158. zebra_day/web/middleware.py +159 -0
  159. zebra_day/web/routers/__init__.py +2 -0
  160. zebra_day/web/routers/api.py +313 -0
  161. zebra_day/web/routers/ui.py +636 -0
  162. zebra_day/zpl_renderer.py +273 -0
  163. zebra_day-2.0.0.dist-info/METADATA +847 -0
  164. zebra_day-2.0.0.dist-info/RECORD +168 -0
  165. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info}/WHEEL +1 -1
  166. zebra_day-2.0.0.dist-info/entry_points.txt +4 -0
  167. zebra_day/bin/scan_for_networed_zebra_printers.py +0 -23
  168. zebra_day/bin/te.py +0 -905
  169. zebra_day/bin/zserve.py +0 -620
  170. zebra_day-0.0.37.dist-info/METADATA +0 -1177
  171. zebra_day-0.0.37.dist-info/RECORD +0 -10
  172. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info/licenses}/LICENSE +0 -0
  173. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,636 @@
1
+ """
2
+ UI router for zebra_day web interface.
3
+
4
+ Provides HTML endpoints for the web-based management interface.
5
+ All routes use the modern UI design with responsive layouts.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import subprocess
12
+ import tempfile
13
+ import time
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ from fastapi import APIRouter, Request, Form, HTTPException
19
+ from fastapi.responses import HTMLResponse, RedirectResponse, Response
20
+
21
+ from zebra_day.logging_config import get_logger
22
+ import zebra_day.cmd_mgr as zdcm
23
+
24
+ _log = get_logger(__name__)
25
+
26
+ router = APIRouter()
27
+
28
+
29
+ def get_template_context(request: Request, **kwargs) -> dict:
30
+ """Build common template context for templates."""
31
+ return {
32
+ "request": request,
33
+ "css_theme": f"static/{request.app.state.css_theme}",
34
+ "local_ip": request.app.state.local_ip,
35
+ **kwargs,
36
+ }
37
+
38
+
39
+ def get_modern_context(request: Request, active_page: str = "", **kwargs) -> dict:
40
+ """Build common template context for modern templates."""
41
+ return {
42
+ "request": request,
43
+ "active_page": active_page,
44
+ "local_ip": request.app.state.local_ip,
45
+ "version": getattr(request.app.state, "version", "0.7.0"),
46
+ "cache_bust": str(int(time.time())),
47
+ **kwargs,
48
+ }
49
+
50
+
51
+ def get_templates_list(pkg_path: Path) -> tuple[list, list]:
52
+ """Get lists of stable and draft templates."""
53
+ styles_dir = pkg_path / "etc" / "label_styles"
54
+ stable_templates = []
55
+ draft_templates = []
56
+
57
+ if styles_dir.exists():
58
+ for f in sorted(styles_dir.iterdir()):
59
+ if f.is_file() and f.suffix == ".zpl":
60
+ stable_templates.append(f.stem)
61
+
62
+ tmps_dir = styles_dir / "tmps"
63
+ if tmps_dir.exists():
64
+ for f in sorted(tmps_dir.iterdir()):
65
+ if f.is_file() and f.suffix == ".zpl":
66
+ draft_templates.append(f.stem)
67
+
68
+ return stable_templates, draft_templates
69
+
70
+
71
+ def get_stats(zp, pkg_path: Path) -> dict:
72
+ """Calculate dashboard statistics."""
73
+ labs = zp.printers.get("labs", {})
74
+ # Count printers via nested 'printers' key (v2 schema)
75
+ total_printers = sum(len(lab_data.get("printers", {})) for lab_data in labs.values())
76
+ stable, draft = get_templates_list(pkg_path)
77
+
78
+ # Count backup files
79
+ bkup_dir = pkg_path / "etc" / "old_printer_config"
80
+ backups = len(list(bkup_dir.iterdir())) if bkup_dir.exists() else 0
81
+
82
+ return {
83
+ "total_labs": len(labs),
84
+ "total_printers": total_printers,
85
+ "online_printers": 0, # Would need to check each printer
86
+ "total_templates": len(stable) + len(draft),
87
+ "backups": backups,
88
+ }
89
+
90
+
91
+ # =============================================================================
92
+ # MODERN UI ROUTES (root level)
93
+ # =============================================================================
94
+
95
+ @router.get("/", response_class=HTMLResponse)
96
+ async def modern_dashboard(request: Request):
97
+ """Modern dashboard - home page."""
98
+ zp = request.app.state.zp
99
+ templates = request.app.state.templates
100
+ pkg_path = request.app.state.pkg_path
101
+
102
+ labs = zp.printers.get("labs", {})
103
+ stats = get_stats(zp, pkg_path)
104
+
105
+ context = get_modern_context(
106
+ request,
107
+ active_page="dashboard",
108
+ labs=labs,
109
+ stats=stats,
110
+ )
111
+ return templates.TemplateResponse("modern/dashboard.html", context)
112
+
113
+
114
+ @router.get("/printers", response_class=HTMLResponse)
115
+ async def modern_printers(request: Request):
116
+ """Modern printers list - all labs."""
117
+ zp = request.app.state.zp
118
+ templates = request.app.state.templates
119
+
120
+ labs = list(zp.printers.get("labs", {}).keys())
121
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
122
+
123
+ context = get_modern_context(
124
+ request,
125
+ active_page="printers",
126
+ labs=labs,
127
+ printers=None,
128
+ lab=None,
129
+ ip_root=ip_root,
130
+ )
131
+ return templates.TemplateResponse("modern/printers.html", context)
132
+
133
+
134
+ @router.get("/printers/{lab}", response_class=HTMLResponse)
135
+ async def modern_printers_by_lab(request: Request, lab: str):
136
+ """Modern printers list for a specific lab."""
137
+ zp = request.app.state.zp
138
+ templates = request.app.state.templates
139
+
140
+ if lab not in zp.printers.get("labs", {}):
141
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
142
+
143
+ lab_data = zp.printers["labs"][lab]
144
+ lab_printers = lab_data.get("printers", {})
145
+
146
+ printers = []
147
+ for name, info in lab_printers.items():
148
+ printers.append({
149
+ "id": name,
150
+ "name": info.get("printer_name") or name, # Display name or fallback to ID
151
+ "printer_name": info.get("printer_name"),
152
+ "ip_address": info.get("ip_address", ""),
153
+ "lab_location": info.get("lab_location"),
154
+ "manufacturer": info.get("manufacturer", "zebra"),
155
+ "model": info.get("model", ""),
156
+ "serial": info.get("serial", ""),
157
+ "label_zpl_styles": info.get("label_zpl_styles", []),
158
+ "status": "online" if info.get("ip_address") else "unknown",
159
+ "notes": info.get("notes", ""),
160
+ })
161
+
162
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
163
+
164
+ context = get_modern_context(
165
+ request,
166
+ active_page="printers",
167
+ labs=list(zp.printers.get("labs", {}).keys()),
168
+ printers=printers,
169
+ lab=lab,
170
+ lab_name=lab_data.get("lab_name", lab),
171
+ available_locations=lab_data.get("available_locations", []),
172
+ ip_root=ip_root,
173
+ )
174
+ return templates.TemplateResponse("modern/printers.html", context)
175
+
176
+
177
+ @router.get("/printers/{lab}/{printer_id}", response_class=HTMLResponse)
178
+ async def modern_printer_detail(request: Request, lab: str, printer_id: str):
179
+ """Modern printer detail page."""
180
+ zp = request.app.state.zp
181
+ templates = request.app.state.templates
182
+
183
+ if lab not in zp.printers.get("labs", {}):
184
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
185
+
186
+ lab_data = zp.printers["labs"][lab]
187
+ lab_printers = lab_data.get("printers", {})
188
+
189
+ if printer_id not in lab_printers:
190
+ raise HTTPException(status_code=404, detail=f"Printer '{printer_id}' not found")
191
+
192
+ printer_info = lab_printers[printer_id]
193
+
194
+ # Try to get printer configuration
195
+ printer_config = ""
196
+ ip_addr = printer_info.get("ip_address", "")
197
+ if ip_addr and ip_addr != "dl_png":
198
+ try:
199
+ printer_config = zdcm.ZebraPrinter(ip_addr).get_configuration()
200
+ except Exception as e:
201
+ printer_config = f"Unable to retrieve config: {e}"
202
+
203
+ context = get_modern_context(
204
+ request,
205
+ active_page="printers",
206
+ printer_id=printer_id,
207
+ printer_name=printer_info.get("printer_name") or printer_id,
208
+ lab=lab,
209
+ lab_name=lab_data.get("lab_name", lab),
210
+ available_locations=lab_data.get("available_locations", []),
211
+ printer_info=printer_info,
212
+ printer_config=printer_config,
213
+ )
214
+ return templates.TemplateResponse("modern/printer_detail.html", context)
215
+
216
+
217
+ @router.get("/print", response_class=HTMLResponse)
218
+ async def modern_print_request(
219
+ request: Request,
220
+ lab: str = "",
221
+ printer: str = "",
222
+ template: str = "",
223
+ ):
224
+ """Modern print request form."""
225
+ zp = request.app.state.zp
226
+ templates = request.app.state.templates
227
+ pkg_path = request.app.state.pkg_path
228
+
229
+ stable_templates, draft_templates = get_templates_list(pkg_path)
230
+ labs_dict = zp.printers.get("labs", {})
231
+
232
+ context = get_modern_context(
233
+ request,
234
+ active_page="print",
235
+ labs=list(labs_dict.keys()),
236
+ labs_dict=json.dumps(labs_dict),
237
+ stable_templates=stable_templates,
238
+ draft_templates=draft_templates,
239
+ selected_lab=lab,
240
+ selected_printer=printer,
241
+ selected_template=template,
242
+ )
243
+ return templates.TemplateResponse("modern/print_request.html", context)
244
+
245
+
246
+ @router.get("/templates", response_class=HTMLResponse)
247
+ async def modern_templates(request: Request):
248
+ """Modern template management page."""
249
+ templates = request.app.state.templates
250
+ pkg_path = request.app.state.pkg_path
251
+
252
+ stable_templates, draft_templates = get_templates_list(pkg_path)
253
+
254
+ context = get_modern_context(
255
+ request,
256
+ active_page="templates",
257
+ stable_templates=stable_templates,
258
+ draft_templates=draft_templates,
259
+ )
260
+ return templates.TemplateResponse("modern/templates.html", context)
261
+
262
+
263
+ @router.get("/templates/edit", response_class=HTMLResponse)
264
+ async def modern_template_edit(
265
+ request: Request,
266
+ filename: str,
267
+ dtype: str = "",
268
+ ):
269
+ """Modern template editor."""
270
+ zp = request.app.state.zp
271
+ templates = request.app.state.templates
272
+ pkg_path = request.app.state.pkg_path
273
+
274
+ if dtype:
275
+ filepath = pkg_path / "etc" / "label_styles" / dtype / filename
276
+ else:
277
+ filepath = pkg_path / "etc" / "label_styles" / filename
278
+
279
+ if not filepath.exists():
280
+ raise HTTPException(status_code=404, detail=f"Template '{filename}' not found")
281
+
282
+ content = filepath.read_text()
283
+ labs_dict = zp.printers.get("labs", {})
284
+
285
+ context = get_modern_context(
286
+ request,
287
+ active_page="templates",
288
+ filename=filename,
289
+ content=content,
290
+ dtype=dtype,
291
+ labs=list(labs_dict.keys()),
292
+ labs_dict=json.dumps(labs_dict),
293
+ )
294
+ return templates.TemplateResponse("modern/template_editor.html", context)
295
+
296
+
297
+ @router.get("/templates/preview")
298
+ async def modern_template_preview(
299
+ request: Request,
300
+ filename: str,
301
+ dtype: str = "",
302
+ ):
303
+ """Generate a PNG preview of a ZPL template.
304
+
305
+ Returns the PNG image directly or redirects to the generated file.
306
+ """
307
+ zp = request.app.state.zp
308
+ pkg_path = request.app.state.pkg_path
309
+
310
+ # Find the template file
311
+ if dtype:
312
+ filepath = pkg_path / "etc" / "label_styles" / dtype / filename
313
+ else:
314
+ # Try with .zpl extension if not provided
315
+ if not filename.endswith(".zpl"):
316
+ filepath = pkg_path / "etc" / "label_styles" / f"{filename}.zpl"
317
+ else:
318
+ filepath = pkg_path / "etc" / "label_styles" / filename
319
+
320
+ if not filepath.exists():
321
+ raise HTTPException(status_code=404, detail=f"Template '{filename}' not found")
322
+
323
+ try:
324
+ # Read template content
325
+ zpl_content = filepath.read_text()
326
+
327
+ # Generate PNG preview
328
+ output_dir = pkg_path / "files"
329
+ output_dir.mkdir(parents=True, exist_ok=True)
330
+
331
+ # Use template name for output file
332
+ template_name = filepath.stem
333
+ output_path = output_dir / f"{template_name}_preview.png"
334
+
335
+ result = zp.generate_label_png(zpl_content, str(output_path), False)
336
+
337
+ # Return redirect to the generated file
338
+ return RedirectResponse(url=f"/files/{template_name}_preview.png", status_code=303)
339
+
340
+ except Exception as e:
341
+ raise HTTPException(status_code=500, detail=f"Preview generation failed: {e}")
342
+
343
+
344
+ @router.get("/config", response_class=HTMLResponse)
345
+ async def modern_config(request: Request):
346
+ """Modern configuration page."""
347
+ zp = request.app.state.zp
348
+ templates = request.app.state.templates
349
+ pkg_path = request.app.state.pkg_path
350
+
351
+ labs = list(zp.printers.get("labs", {}).keys())
352
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
353
+
354
+ # Build config summary
355
+ stats = get_stats(zp, pkg_path)
356
+ config_summary = {
357
+ "labs": stats["total_labs"],
358
+ "printers": stats["total_printers"],
359
+ "templates": stats["total_templates"],
360
+ "backups": stats["backups"],
361
+ }
362
+
363
+ context = get_modern_context(
364
+ request,
365
+ active_page="config",
366
+ labs=labs,
367
+ ip_root=ip_root,
368
+ config_summary=config_summary,
369
+ )
370
+ return templates.TemplateResponse("modern/config.html", context)
371
+
372
+
373
+ @router.get("/config/view", response_class=HTMLResponse)
374
+ async def modern_config_view(request: Request):
375
+ """View printer configuration JSON."""
376
+ zp = request.app.state.zp
377
+ templates = request.app.state.templates
378
+
379
+ config_json = json.dumps(zp.printers, indent=4)
380
+
381
+ context = get_template_context(
382
+ request,
383
+ title="View Configuration",
384
+ config_json=config_json,
385
+ mode="view",
386
+ )
387
+ return templates.TemplateResponse("modern/config_editor.html", context)
388
+
389
+
390
+ @router.get("/config/edit", response_class=HTMLResponse)
391
+ async def modern_config_edit(request: Request, error_msg: Optional[str] = None):
392
+ """Edit printer configuration JSON."""
393
+ zp = request.app.state.zp
394
+ templates = request.app.state.templates
395
+
396
+ config_json = json.dumps(zp.printers, indent=4)
397
+
398
+ context = get_template_context(
399
+ request,
400
+ title="Edit Configuration",
401
+ config_json=config_json,
402
+ mode="edit",
403
+ error_msg=error_msg,
404
+ )
405
+ return templates.TemplateResponse("modern/config_editor.html", context)
406
+
407
+
408
+ @router.post("/config/save")
409
+ async def modern_config_save(request: Request, json_data: str = Form(...)):
410
+ """Save edited printer configuration JSON."""
411
+ zp = request.app.state.zp
412
+ pkg_path = request.app.state.pkg_path
413
+
414
+ try:
415
+ # Validate JSON
416
+ new_config = json.loads(json_data)
417
+
418
+ # Backup current config
419
+ json_file = pkg_path / "etc" / "printer_config.json"
420
+ if json_file.exists():
421
+ bkup_dir = pkg_path / "etc" / "old_printer_config"
422
+ bkup_dir.mkdir(parents=True, exist_ok=True)
423
+ rec_date = str(datetime.now()).replace(" ", "_")
424
+ bkup_file = bkup_dir / f"printer_config.{rec_date}.json"
425
+ bkup_file.write_text(json_file.read_text())
426
+
427
+ # Save new config
428
+ json_file.write_text(json.dumps(new_config, indent=4))
429
+
430
+ # Reload config
431
+ zp.load_printer_json(json_file=zp.printers_filename, relative=False)
432
+
433
+ return RedirectResponse(url="/config", status_code=303)
434
+
435
+ except json.JSONDecodeError as e:
436
+ return RedirectResponse(
437
+ url=f"/config/edit?error_msg=Invalid JSON: {e}",
438
+ status_code=303
439
+ )
440
+
441
+
442
+ @router.get("/config/backups", response_class=HTMLResponse)
443
+ async def modern_config_backups(request: Request):
444
+ """List prior config files."""
445
+ templates = request.app.state.templates
446
+ pkg_path = request.app.state.pkg_path
447
+ bkup_dir = pkg_path / "etc" / "old_printer_config"
448
+
449
+ backup_files = []
450
+ if bkup_dir.exists():
451
+ for f in sorted(bkup_dir.iterdir(), reverse=True):
452
+ if f.is_file():
453
+ backup_files.append(f.name)
454
+
455
+ context = get_template_context(
456
+ request,
457
+ title="Configuration Backups",
458
+ backup_files=backup_files,
459
+ )
460
+ return templates.TemplateResponse("modern/config_backups.html", context)
461
+
462
+
463
+ @router.get("/config/new", response_class=HTMLResponse)
464
+ async def modern_config_new(request: Request):
465
+ """Build new config page."""
466
+ zp = request.app.state.zp
467
+ templates = request.app.state.templates
468
+
469
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
470
+
471
+ context = get_template_context(
472
+ request,
473
+ title="New Configuration",
474
+ ip_root=ip_root,
475
+ labs=list(zp.printers.get("labs", {}).keys()),
476
+ )
477
+ return templates.TemplateResponse("modern/config_new.html", context)
478
+
479
+
480
+ @router.get("/config/reset")
481
+ async def modern_config_reset(request: Request):
482
+ """Reset printer config from template."""
483
+ zp = request.app.state.zp
484
+ zp.replace_printer_json_from_template()
485
+ time.sleep(1.2)
486
+ return RedirectResponse(url="/config", status_code=303)
487
+
488
+
489
+ @router.get("/config/clear")
490
+ async def modern_config_clear(request: Request):
491
+ """Clear the printer configuration JSON."""
492
+ zp = request.app.state.zp
493
+ zp.clear_printers_json()
494
+ time.sleep(1.2)
495
+ return RedirectResponse(url="/config", status_code=303)
496
+
497
+
498
+ @router.get("/config/scan", response_class=HTMLResponse)
499
+ async def modern_config_scan(
500
+ request: Request,
501
+ ip_stub: str = "192.168.1",
502
+ scan_wait: str = "0.25",
503
+ lab: str = "scan-results",
504
+ ):
505
+ """Scan network for printers."""
506
+ zp = request.app.state.zp
507
+ zp.probe_zebra_printers_add_to_printers_json(
508
+ ip_stub=ip_stub, scan_wait=scan_wait, lab=lab
509
+ )
510
+ time.sleep(2.2)
511
+ return RedirectResponse(url=f"/printers/{lab}", status_code=303)
512
+
513
+
514
+ @router.get("/_print_label", response_class=HTMLResponse)
515
+ async def modern_print_label(
516
+ request: Request,
517
+ lab: Optional[str] = None,
518
+ printer: str = "",
519
+ printer_ip: str = "",
520
+ label_zpl_style: str = "",
521
+ uid_barcode: str = "",
522
+ alt_a: str = "",
523
+ alt_b: str = "",
524
+ alt_c: str = "",
525
+ alt_d: str = "",
526
+ alt_e: str = "",
527
+ alt_f: str = "",
528
+ labSelect: str = "",
529
+ ):
530
+ """Execute print request - modern UI."""
531
+ zp = request.app.state.zp
532
+ templates = request.app.state.templates
533
+ rate_limiter = request.app.state.print_rate_limiter
534
+
535
+ if lab is None:
536
+ lab = labSelect
537
+
538
+ client_ip = request.client.host if request.client else "unknown"
539
+
540
+ # Check rate limit
541
+ allowed, reason = await rate_limiter.acquire(client_ip)
542
+ if not allowed:
543
+ raise HTTPException(status_code=429, detail=reason)
544
+
545
+ try:
546
+ result = zp.print_zpl(
547
+ lab=lab,
548
+ printer_name=printer,
549
+ label_zpl_style=label_zpl_style,
550
+ uid_barcode=uid_barcode,
551
+ alt_a=alt_a,
552
+ alt_b=alt_b,
553
+ alt_c=alt_c,
554
+ alt_d=alt_d,
555
+ alt_e=alt_e,
556
+ alt_f=alt_f,
557
+ client_ip=client_ip,
558
+ )
559
+ finally:
560
+ rate_limiter.release()
561
+
562
+ # Build the full URL for reference
563
+ full_url = str(request.url)
564
+
565
+ png_url = None
566
+ if result and ".png" in str(result):
567
+ png_name = str(result).split("/")[-1]
568
+ png_url = f"/files/{png_name}"
569
+
570
+ context = get_modern_context(
571
+ request,
572
+ title="Print Result",
573
+ success=True,
574
+ full_url=full_url,
575
+ png_url=png_url,
576
+ )
577
+ return templates.TemplateResponse("modern/print_result.html", context)
578
+
579
+
580
+ @router.post("/save", response_class=HTMLResponse)
581
+ async def modern_save_template(
582
+ request: Request,
583
+ filename: str = Form(...),
584
+ content: str = Form(...),
585
+ ftag: str = Form("na"),
586
+ lab: str = Form(""),
587
+ printer: str = Form(""),
588
+ ):
589
+ """Save ZPL template as a new draft file - modern UI."""
590
+ templates = request.app.state.templates
591
+ pkg_path = request.app.state.pkg_path
592
+
593
+ rec_date = str(datetime.now()).replace(" ", "_")
594
+ new_filename = filename.replace(".zpl", f".{ftag}.{rec_date}.zpl")
595
+
596
+ tmps_dir = pkg_path / "etc" / "label_styles" / "tmps"
597
+ tmps_dir.mkdir(parents=True, exist_ok=True)
598
+
599
+ temp_filepath = tmps_dir / new_filename
600
+ temp_filepath.write_text(content)
601
+
602
+ context = get_modern_context(
603
+ request,
604
+ title="Template Saved",
605
+ new_filename=new_filename,
606
+ )
607
+ return templates.TemplateResponse("modern/save_result.html", context)
608
+
609
+
610
+ @router.post("/png_renderer")
611
+ async def modern_png_renderer(
612
+ request: Request,
613
+ filename: str = Form(...),
614
+ content: str = Form(...),
615
+ lab: str = Form(""),
616
+ printer: str = Form(""),
617
+ ftag: str = Form(""),
618
+ ):
619
+ """Render ZPL content to PNG - modern UI."""
620
+ zp = request.app.state.zp
621
+ pkg_path = request.app.state.pkg_path
622
+
623
+ files_dir = pkg_path / "files"
624
+ files_dir.mkdir(parents=True, exist_ok=True)
625
+
626
+ png_tmp_f = tempfile.NamedTemporaryFile(
627
+ suffix=".png", dir=str(files_dir), delete=False
628
+ ).name
629
+
630
+ zp.generate_label_png(content, png_fn=png_tmp_f)
631
+
632
+ # Return just the relative path for the img src
633
+ return Response(
634
+ content=f"files/{Path(png_tmp_f).name}",
635
+ media_type="text/plain",
636
+ )