zebra-day 0.0.32__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. zebra_day/__init__.py +35 -0
  2. zebra_day/bin/fetch_zebra_config.py +15 -0
  3. zebra_day/bin/generate_coord_grid_zpl.py +50 -0
  4. zebra_day/bin/print_zpl_from_file.py +21 -0
  5. zebra_day/bin/probe_new_label_dimensions.py +75 -0
  6. zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +2 -1
  7. zebra_day/bin/zserve.py +701 -259
  8. zebra_day/cli/__init__.py +240 -0
  9. zebra_day/cli/cognito.py +121 -0
  10. zebra_day/cli/gui.py +255 -0
  11. zebra_day/cli/printer.py +168 -0
  12. zebra_day/cli/template.py +176 -0
  13. zebra_day/cmd_mgr.py +35 -0
  14. zebra_day/etc/label_styles/blank.zpl +0 -0
  15. zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
  16. zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
  17. zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
  18. zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
  19. zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
  20. zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
  21. zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
  22. zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
  23. zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
  24. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
  25. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
  26. zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
  27. zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
  28. zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
  29. zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
  30. zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
  31. zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
  32. zebra_day/etc/label_styles/tube_2inX0.5in.zpl +15 -0
  33. zebra_day/etc/label_styles/tube_2inX0.5inHD.zpl +15 -0
  34. zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
  35. zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
  36. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
  37. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
  38. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
  39. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
  40. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
  41. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
  42. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
  43. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
  44. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
  45. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
  46. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
  47. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
  48. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
  49. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
  50. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
  51. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
  52. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
  53. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
  54. zebra_day/etc/old_printer_config/{2023-10-25_02:19:04.139607_printer_config.json → 2026-02-01_01:51:29.739070_printer_config.json} +4 -3
  55. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
  56. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
  57. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
  58. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
  59. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
  60. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
  61. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
  62. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
  63. zebra_day/etc/printer_config.json +3 -54
  64. zebra_day/etc/printer_config.template.json +3 -2
  65. zebra_day/etc/tmp_printers0.json +5 -0
  66. zebra_day/etc/tmp_printers374.json +5 -0
  67. zebra_day/etc/tmp_printers383.json +5 -0
  68. zebra_day/etc/tmp_printers450.json +5 -0
  69. zebra_day/etc/tmp_printers504.json +5 -0
  70. zebra_day/etc/tmp_printers608.json +5 -0
  71. zebra_day/etc/tmp_printers657.json +5 -0
  72. zebra_day/etc/tmp_printers838.json +5 -0
  73. zebra_day/etc/tmp_printers839.json +5 -0
  74. zebra_day/etc/tmp_printers933.json +5 -0
  75. zebra_day/etc/tmp_printers957.json +5 -0
  76. zebra_day/exceptions.py +88 -0
  77. zebra_day/files/hold +0 -0
  78. zebra_day/files/test_png_17696.png +0 -0
  79. zebra_day/files/test_png_23477.png +0 -0
  80. zebra_day/files/test_png_28157.png +0 -0
  81. zebra_day/files/test_png_35832.png +0 -0
  82. zebra_day/files/test_png_36400.png +0 -0
  83. zebra_day/files/test_png_40816.png +0 -0
  84. zebra_day/files/test_png_49564.png +0 -0
  85. zebra_day/files/test_png_53848.png +0 -0
  86. zebra_day/files/test_png_62542.png +0 -0
  87. zebra_day/files/test_png_91597.png +0 -0
  88. zebra_day/files/test_png_93633.png +0 -0
  89. zebra_day/files/tmpbjo3k7q1.png +0 -0
  90. zebra_day/files/tmpigtr4pwy.png +0 -0
  91. zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
  92. zebra_day/logging_config.py +74 -0
  93. zebra_day/logs/.hold +0 -0
  94. zebra_day/logs/print_requests.log +2 -0
  95. zebra_day/paths.py +143 -0
  96. zebra_day/print_mgr.py +489 -103
  97. zebra_day/static/datschund.css +63 -43
  98. zebra_day/static/datschund.png +0 -0
  99. zebra_day/static/daylily.png +0 -0
  100. zebra_day/static/favicon.svg +20 -0
  101. zebra_day/static/general.css +99 -0
  102. zebra_day/static/js/zebra_modern.js +172 -0
  103. zebra_day/static/lsmc.css +354 -0
  104. zebra_day/static/moon.jpeg +0 -0
  105. zebra_day/static/oakland.css +0 -32
  106. zebra_day/static/popday_daylily.css +1 -1
  107. zebra_day/static/style.css +39 -0
  108. zebra_day/static/zebra_modern.css +771 -0
  109. zebra_day/templates/base.html +36 -0
  110. zebra_day/templates/bpr.html +72 -0
  111. zebra_day/templates/build_new_config.html +36 -0
  112. zebra_day/templates/build_print_request.html +32 -0
  113. zebra_day/templates/chg_ui_style.html +19 -0
  114. zebra_day/templates/edit_template.html +128 -0
  115. zebra_day/templates/edit_zpl.html +37 -0
  116. zebra_day/templates/index.html +82 -0
  117. zebra_day/templates/legacy/base.html +37 -0
  118. zebra_day/templates/legacy/bpr.html +72 -0
  119. zebra_day/templates/legacy/build_new_config.html +36 -0
  120. zebra_day/templates/legacy/build_print_request.html +32 -0
  121. zebra_day/templates/legacy/chg_ui_style.html +19 -0
  122. zebra_day/templates/legacy/edit_template.html +128 -0
  123. zebra_day/templates/legacy/edit_zpl.html +37 -0
  124. zebra_day/templates/legacy/index.html +82 -0
  125. zebra_day/templates/legacy/list_prior_configs.html +24 -0
  126. zebra_day/templates/legacy/print_result.html +30 -0
  127. zebra_day/templates/legacy/printer_details.html +25 -0
  128. zebra_day/templates/legacy/printer_status.html +70 -0
  129. zebra_day/templates/legacy/save_result.html +17 -0
  130. zebra_day/templates/legacy/send_print_request.html +34 -0
  131. zebra_day/templates/legacy/simple_print.html +94 -0
  132. zebra_day/templates/legacy/view_pstation_json.html +29 -0
  133. zebra_day/templates/list_prior_configs.html +24 -0
  134. zebra_day/templates/modern/base.html +98 -0
  135. zebra_day/templates/modern/config.html +141 -0
  136. zebra_day/templates/modern/dashboard.html +160 -0
  137. zebra_day/templates/modern/print_request.html +141 -0
  138. zebra_day/templates/modern/print_result.html +88 -0
  139. zebra_day/templates/modern/printer_detail.html +117 -0
  140. zebra_day/templates/modern/printers.html +133 -0
  141. zebra_day/templates/modern/save_result.html +46 -0
  142. zebra_day/templates/modern/template_editor.html +172 -0
  143. zebra_day/templates/modern/templates.html +122 -0
  144. zebra_day/templates/print_result.html +30 -0
  145. zebra_day/templates/printer_details.html +25 -0
  146. zebra_day/templates/printer_status.html +70 -0
  147. zebra_day/templates/save_result.html +17 -0
  148. zebra_day/templates/send_print_request.html +34 -0
  149. zebra_day/templates/simple_print.html +94 -0
  150. zebra_day/templates/view_pstation_json.html +29 -0
  151. zebra_day/web/__init__.py +9 -0
  152. zebra_day/web/app.py +171 -0
  153. zebra_day/web/auth.py +172 -0
  154. zebra_day/web/middleware.py +159 -0
  155. zebra_day/web/routers/__init__.py +2 -0
  156. zebra_day/web/routers/api.py +163 -0
  157. zebra_day/web/routers/ui.py +1051 -0
  158. zebra_day/zpl_renderer.py +273 -0
  159. zebra_day-1.0.2.dist-info/METADATA +786 -0
  160. zebra_day-1.0.2.dist-info/RECORD +179 -0
  161. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/WHEEL +1 -1
  162. zebra_day-1.0.2.dist-info/entry_points.txt +4 -0
  163. zebra_day/etc/.blind +0 -1
  164. zebra_day/etc/current_style.txt +0 -1
  165. zebra_day/etc/label_styles/test_2inX1in.zpl +0 -22
  166. zebra_day/etc/label_styles/tmps/labware_2inX1in.na.2023-10-24_12:49:21.045127.zpl +0 -21
  167. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.8888.2023-10-24_16:02:16.443911.zpl +0 -21
  168. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.zpl +0 -21
  169. zebra_day/etc/label_styles/tmps/test_2inX1in.na.2023-10-24_12:45:37.002774.zpl +0 -22
  170. zebra_day/etc/old_printer_config/2023-10-24_16:06:06.931764_printer_config.json +0 -67
  171. zebra_day/etc/printer_config.json~ +0 -67
  172. zebra_day/files/tmp_2olihg4.png +0 -0
  173. zebra_day/files/tmpveojoyvn.png +0 -0
  174. zebra_day/files/zpl_label_labware_2inX1in_2023-10-25_02:30:08.093631.png +0 -0
  175. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_15:54:29.343124.png +0 -0
  176. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_16:01:45.670132.png +0 -0
  177. zebra_day/static/beyonce.css +0 -227
  178. zebra_day/static/datschund_on_moon.css +0 -164
  179. zebra_day/static/medicalsci.css +0 -144
  180. zebra_day/static/moar_zebra.css +0 -133
  181. zebra_day/static/popday.css +0 -140
  182. zebra_day/static/popday_dark.css +0 -140
  183. zebra_day/static/popday_dog.css +0 -140
  184. zebra_day-0.0.32.dist-info/METADATA +0 -14
  185. zebra_day-0.0.32.dist-info/RECORD +0 -53
  186. zebra_day-0.0.32.dist-info/entry_points.txt +0 -2
  187. /zebra_day/{etc/label_styles/blank_0inX0in.zpl → bin/__init__.py} +0 -0
  188. /zebra_day/etc/label_styles/{labware_2inX1in.zpl → generic_2inX1in.zpl} +0 -0
  189. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info/licenses}/LICENSE +0 -0
  190. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1051 @@
1
+ """
2
+ UI router for zebra_day web interface.
3
+
4
+ Provides HTML endpoints for the web-based management interface.
5
+ Supports dual interfaces:
6
+ - Modern UI: Root routes (/, /printers, /print, /templates, /config)
7
+ - Legacy UI: Routes under /legacy prefix
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import tempfile
15
+ import time
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ from fastapi import APIRouter, Request, Form, HTTPException
21
+ from fastapi.responses import HTMLResponse, RedirectResponse, Response
22
+
23
+ from zebra_day.logging_config import get_logger
24
+ import zebra_day.cmd_mgr as zdcm
25
+
26
+ _log = get_logger(__name__)
27
+
28
+ router = APIRouter()
29
+
30
+
31
+ def get_template_context(request: Request, **kwargs) -> dict:
32
+ """Build common template context for legacy templates."""
33
+ return {
34
+ "request": request,
35
+ "css_theme": f"static/{request.app.state.css_theme}",
36
+ "local_ip": request.app.state.local_ip,
37
+ **kwargs,
38
+ }
39
+
40
+
41
+ def get_modern_context(request: Request, active_page: str = "", **kwargs) -> dict:
42
+ """Build common template context for modern templates."""
43
+ return {
44
+ "request": request,
45
+ "active_page": active_page,
46
+ "local_ip": request.app.state.local_ip,
47
+ "version": getattr(request.app.state, "version", "0.7.0"),
48
+ "cache_bust": str(int(time.time())),
49
+ **kwargs,
50
+ }
51
+
52
+
53
+ def get_templates_list(pkg_path: Path) -> tuple[list, list]:
54
+ """Get lists of stable and draft templates."""
55
+ styles_dir = pkg_path / "etc" / "label_styles"
56
+ stable_templates = []
57
+ draft_templates = []
58
+
59
+ if styles_dir.exists():
60
+ for f in sorted(styles_dir.iterdir()):
61
+ if f.is_file() and f.suffix == ".zpl":
62
+ stable_templates.append(f.stem)
63
+
64
+ tmps_dir = styles_dir / "tmps"
65
+ if tmps_dir.exists():
66
+ for f in sorted(tmps_dir.iterdir()):
67
+ if f.is_file() and f.suffix == ".zpl":
68
+ draft_templates.append(f.stem)
69
+
70
+ return stable_templates, draft_templates
71
+
72
+
73
+ def get_stats(zp, pkg_path: Path) -> dict:
74
+ """Calculate dashboard statistics."""
75
+ labs = zp.printers.get("labs", {})
76
+ total_printers = sum(len(printers) for printers in labs.values())
77
+ stable, draft = get_templates_list(pkg_path)
78
+
79
+ # Count backup files
80
+ bkup_dir = pkg_path / "etc" / "old_printer_config"
81
+ backups = len(list(bkup_dir.iterdir())) if bkup_dir.exists() else 0
82
+
83
+ return {
84
+ "total_labs": len(labs),
85
+ "total_printers": total_printers,
86
+ "online_printers": 0, # Would need to check each printer
87
+ "total_templates": len(stable) + len(draft),
88
+ "backups": backups,
89
+ }
90
+
91
+
92
+ # =============================================================================
93
+ # MODERN UI ROUTES (root level)
94
+ # =============================================================================
95
+
96
+ @router.get("/", response_class=HTMLResponse)
97
+ async def modern_dashboard(request: Request):
98
+ """Modern dashboard - home page."""
99
+ zp = request.app.state.zp
100
+ templates = request.app.state.templates
101
+ pkg_path = request.app.state.pkg_path
102
+
103
+ labs = zp.printers.get("labs", {})
104
+ stats = get_stats(zp, pkg_path)
105
+
106
+ context = get_modern_context(
107
+ request,
108
+ active_page="dashboard",
109
+ labs=labs,
110
+ stats=stats,
111
+ )
112
+ return templates.TemplateResponse("modern/dashboard.html", context)
113
+
114
+
115
+ @router.get("/printers", response_class=HTMLResponse)
116
+ async def modern_printers(request: Request):
117
+ """Modern printers list - all labs."""
118
+ zp = request.app.state.zp
119
+ templates = request.app.state.templates
120
+
121
+ labs = list(zp.printers.get("labs", {}).keys())
122
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
123
+
124
+ context = get_modern_context(
125
+ request,
126
+ active_page="printers",
127
+ labs=labs,
128
+ printers=None,
129
+ lab=None,
130
+ ip_root=ip_root,
131
+ )
132
+ return templates.TemplateResponse("modern/printers.html", context)
133
+
134
+
135
+ @router.get("/printers/{lab}", response_class=HTMLResponse)
136
+ async def modern_printers_by_lab(request: Request, lab: str):
137
+ """Modern printers list for a specific lab."""
138
+ zp = request.app.state.zp
139
+ templates = request.app.state.templates
140
+
141
+ if lab not in zp.printers.get("labs", {}):
142
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
143
+
144
+ printers = []
145
+ for name, info in zp.printers["labs"][lab].items():
146
+ printers.append({
147
+ "name": name,
148
+ "ip_address": info.get("ip_address", ""),
149
+ "model": info.get("model", ""),
150
+ "serial": info.get("serial", ""),
151
+ "label_zpl_styles": info.get("label_zpl_styles", []),
152
+ "status": "online" if info.get("ip_address") else "unknown",
153
+ })
154
+
155
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
156
+
157
+ context = get_modern_context(
158
+ request,
159
+ active_page="printers",
160
+ labs=list(zp.printers.get("labs", {}).keys()),
161
+ printers=printers,
162
+ lab=lab,
163
+ ip_root=ip_root,
164
+ )
165
+ return templates.TemplateResponse("modern/printers.html", context)
166
+
167
+
168
+ @router.get("/printers/{lab}/{printer_name}", response_class=HTMLResponse)
169
+ async def modern_printer_detail(request: Request, lab: str, printer_name: str):
170
+ """Modern printer detail page."""
171
+ zp = request.app.state.zp
172
+ templates = request.app.state.templates
173
+
174
+ if lab not in zp.printers.get("labs", {}):
175
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
176
+ if printer_name not in zp.printers["labs"][lab]:
177
+ raise HTTPException(status_code=404, detail=f"Printer '{printer_name}' not found")
178
+
179
+ printer_info = zp.printers["labs"][lab][printer_name]
180
+
181
+ # Try to get printer configuration
182
+ printer_config = ""
183
+ ip_addr = printer_info.get("ip_address", "")
184
+ if ip_addr and ip_addr != "dl_png":
185
+ try:
186
+ printer_config = zdcm.ZebraPrinter(ip_addr).get_configuration()
187
+ except Exception as e:
188
+ printer_config = f"Unable to retrieve config: {e}"
189
+
190
+ context = get_modern_context(
191
+ request,
192
+ active_page="printers",
193
+ printer_name=printer_name,
194
+ lab=lab,
195
+ printer_info=printer_info,
196
+ printer_config=printer_config,
197
+ )
198
+ return templates.TemplateResponse("modern/printer_detail.html", context)
199
+
200
+
201
+ @router.get("/print", response_class=HTMLResponse)
202
+ async def modern_print_request(
203
+ request: Request,
204
+ lab: str = "",
205
+ printer: str = "",
206
+ template: str = "",
207
+ ):
208
+ """Modern print request form."""
209
+ zp = request.app.state.zp
210
+ templates = request.app.state.templates
211
+ pkg_path = request.app.state.pkg_path
212
+
213
+ stable_templates, draft_templates = get_templates_list(pkg_path)
214
+ labs_dict = zp.printers.get("labs", {})
215
+
216
+ context = get_modern_context(
217
+ request,
218
+ active_page="print",
219
+ labs=list(labs_dict.keys()),
220
+ labs_dict=json.dumps(labs_dict),
221
+ stable_templates=stable_templates,
222
+ draft_templates=draft_templates,
223
+ selected_lab=lab,
224
+ selected_printer=printer,
225
+ selected_template=template,
226
+ )
227
+ return templates.TemplateResponse("modern/print_request.html", context)
228
+
229
+
230
+ @router.get("/templates", response_class=HTMLResponse)
231
+ async def modern_templates(request: Request):
232
+ """Modern template management page."""
233
+ templates = request.app.state.templates
234
+ pkg_path = request.app.state.pkg_path
235
+
236
+ stable_templates, draft_templates = get_templates_list(pkg_path)
237
+
238
+ context = get_modern_context(
239
+ request,
240
+ active_page="templates",
241
+ stable_templates=stable_templates,
242
+ draft_templates=draft_templates,
243
+ )
244
+ return templates.TemplateResponse("modern/templates.html", context)
245
+
246
+
247
+ @router.get("/templates/edit", response_class=HTMLResponse)
248
+ async def modern_template_edit(
249
+ request: Request,
250
+ filename: str,
251
+ dtype: str = "",
252
+ ):
253
+ """Modern template editor."""
254
+ zp = request.app.state.zp
255
+ templates = request.app.state.templates
256
+ pkg_path = request.app.state.pkg_path
257
+
258
+ if dtype:
259
+ filepath = pkg_path / "etc" / "label_styles" / dtype / filename
260
+ else:
261
+ filepath = pkg_path / "etc" / "label_styles" / filename
262
+
263
+ if not filepath.exists():
264
+ raise HTTPException(status_code=404, detail=f"Template '{filename}' not found")
265
+
266
+ content = filepath.read_text()
267
+ labs_dict = zp.printers.get("labs", {})
268
+
269
+ context = get_modern_context(
270
+ request,
271
+ active_page="templates",
272
+ filename=filename,
273
+ content=content,
274
+ dtype=dtype,
275
+ labs=list(labs_dict.keys()),
276
+ labs_dict=json.dumps(labs_dict),
277
+ )
278
+ return templates.TemplateResponse("modern/template_editor.html", context)
279
+
280
+
281
+ @router.get("/config", response_class=HTMLResponse)
282
+ async def modern_config(request: Request):
283
+ """Modern configuration page."""
284
+ zp = request.app.state.zp
285
+ templates = request.app.state.templates
286
+ pkg_path = request.app.state.pkg_path
287
+
288
+ labs = list(zp.printers.get("labs", {}).keys())
289
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
290
+
291
+ # Build config summary
292
+ stats = get_stats(zp, pkg_path)
293
+ config_summary = {
294
+ "labs": stats["total_labs"],
295
+ "printers": stats["total_printers"],
296
+ "templates": stats["total_templates"],
297
+ "backups": stats["backups"],
298
+ }
299
+
300
+ context = get_modern_context(
301
+ request,
302
+ active_page="config",
303
+ labs=labs,
304
+ ip_root=ip_root,
305
+ config_summary=config_summary,
306
+ )
307
+ return templates.TemplateResponse("modern/config.html", context)
308
+
309
+
310
+ @router.get("/config/view", response_class=HTMLResponse)
311
+ async def modern_config_view(request: Request):
312
+ """View printer configuration JSON (redirects to legacy for now)."""
313
+ return RedirectResponse(url="/legacy/view_pstation_json", status_code=303)
314
+
315
+
316
+ @router.get("/config/edit", response_class=HTMLResponse)
317
+ async def modern_config_edit(request: Request):
318
+ """Edit printer configuration JSON (redirects to legacy for now)."""
319
+ return RedirectResponse(url="/legacy/view_pstation_json", status_code=303)
320
+
321
+
322
+ @router.get("/config/backups", response_class=HTMLResponse)
323
+ async def modern_config_backups(request: Request):
324
+ """List prior config files (redirects to legacy for now)."""
325
+ return RedirectResponse(url="/legacy/list_prior_printer_config_files", status_code=303)
326
+
327
+
328
+ @router.get("/config/new", response_class=HTMLResponse)
329
+ async def modern_config_new(request: Request):
330
+ """Build new config (redirects to legacy for now)."""
331
+ return RedirectResponse(url="/legacy/build_new_printers_config_json", status_code=303)
332
+
333
+
334
+ @router.get("/config/scan", response_class=HTMLResponse)
335
+ async def modern_config_scan(
336
+ request: Request,
337
+ ip_stub: str = "192.168.1",
338
+ scan_wait: str = "0.25",
339
+ lab: str = "scan-results",
340
+ ):
341
+ """Scan network for printers."""
342
+ zp = request.app.state.zp
343
+ zp.probe_zebra_printers_add_to_printers_json(
344
+ ip_stub=ip_stub, scan_wait=scan_wait, lab=lab
345
+ )
346
+ time.sleep(2.2)
347
+ return RedirectResponse(url=f"/printers/{lab}", status_code=303)
348
+
349
+
350
+ @router.get("/_print_label", response_class=HTMLResponse)
351
+ async def modern_print_label(
352
+ request: Request,
353
+ lab: Optional[str] = None,
354
+ printer: str = "",
355
+ printer_ip: str = "",
356
+ label_zpl_style: str = "",
357
+ uid_barcode: str = "",
358
+ alt_a: str = "",
359
+ alt_b: str = "",
360
+ alt_c: str = "",
361
+ alt_d: str = "",
362
+ alt_e: str = "",
363
+ alt_f: str = "",
364
+ labSelect: str = "",
365
+ ):
366
+ """Execute print request - modern UI."""
367
+ zp = request.app.state.zp
368
+ templates = request.app.state.templates
369
+ rate_limiter = request.app.state.print_rate_limiter
370
+
371
+ if lab is None:
372
+ lab = labSelect
373
+
374
+ client_ip = request.client.host if request.client else "unknown"
375
+
376
+ # Check rate limit
377
+ allowed, reason = await rate_limiter.acquire(client_ip)
378
+ if not allowed:
379
+ raise HTTPException(status_code=429, detail=reason)
380
+
381
+ try:
382
+ result = zp.print_zpl(
383
+ lab=lab,
384
+ printer_name=printer,
385
+ label_zpl_style=label_zpl_style,
386
+ uid_barcode=uid_barcode,
387
+ alt_a=alt_a,
388
+ alt_b=alt_b,
389
+ alt_c=alt_c,
390
+ alt_d=alt_d,
391
+ alt_e=alt_e,
392
+ alt_f=alt_f,
393
+ client_ip=client_ip,
394
+ )
395
+ finally:
396
+ rate_limiter.release()
397
+
398
+ # Build the full URL for reference
399
+ full_url = str(request.url)
400
+
401
+ png_url = None
402
+ if result and ".png" in str(result):
403
+ png_name = str(result).split("/")[-1]
404
+ png_url = f"/files/{png_name}"
405
+
406
+ context = get_modern_context(
407
+ request,
408
+ title="Print Result",
409
+ success=True,
410
+ full_url=full_url,
411
+ png_url=png_url,
412
+ )
413
+ return templates.TemplateResponse("modern/print_result.html", context)
414
+
415
+
416
+ @router.post("/save", response_class=HTMLResponse)
417
+ async def modern_save_template(
418
+ request: Request,
419
+ filename: str = Form(...),
420
+ content: str = Form(...),
421
+ ftag: str = Form("na"),
422
+ lab: str = Form(""),
423
+ printer: str = Form(""),
424
+ ):
425
+ """Save ZPL template as a new draft file - modern UI."""
426
+ templates = request.app.state.templates
427
+ pkg_path = request.app.state.pkg_path
428
+
429
+ rec_date = str(datetime.now()).replace(" ", "_")
430
+ new_filename = filename.replace(".zpl", f".{ftag}.{rec_date}.zpl")
431
+
432
+ tmps_dir = pkg_path / "etc" / "label_styles" / "tmps"
433
+ tmps_dir.mkdir(parents=True, exist_ok=True)
434
+
435
+ temp_filepath = tmps_dir / new_filename
436
+ temp_filepath.write_text(content)
437
+
438
+ context = get_modern_context(
439
+ request,
440
+ title="Template Saved",
441
+ new_filename=new_filename,
442
+ )
443
+ return templates.TemplateResponse("modern/save_result.html", context)
444
+
445
+
446
+ @router.post("/png_renderer")
447
+ async def modern_png_renderer(
448
+ request: Request,
449
+ filename: str = Form(...),
450
+ content: str = Form(...),
451
+ lab: str = Form(""),
452
+ printer: str = Form(""),
453
+ ftag: str = Form(""),
454
+ ):
455
+ """Render ZPL content to PNG - modern UI."""
456
+ zp = request.app.state.zp
457
+ pkg_path = request.app.state.pkg_path
458
+
459
+ files_dir = pkg_path / "files"
460
+ files_dir.mkdir(parents=True, exist_ok=True)
461
+
462
+ png_tmp_f = tempfile.NamedTemporaryFile(
463
+ suffix=".png", dir=str(files_dir), delete=False
464
+ ).name
465
+
466
+ zp.generate_label_png(content, png_fn=png_tmp_f)
467
+
468
+ # Return just the relative path for the img src
469
+ return Response(
470
+ content=f"files/{Path(png_tmp_f).name}",
471
+ media_type="text/plain",
472
+ )
473
+
474
+
475
+ # =============================================================================
476
+ # LEGACY UI ROUTES (under /legacy prefix)
477
+ # =============================================================================
478
+
479
+ @router.get("/legacy", response_class=HTMLResponse)
480
+ async def legacy_index(request: Request):
481
+ """Legacy home page."""
482
+ zp = request.app.state.zp
483
+ templates = request.app.state.templates
484
+
485
+ labs = list(zp.printers.get("labs", {}).keys())
486
+
487
+ context = get_template_context(
488
+ request,
489
+ title="Zebra Day - Home",
490
+ labs=labs,
491
+ )
492
+ return templates.TemplateResponse("legacy/index.html", context)
493
+
494
+
495
+ @router.get("/legacy/printer_status", response_class=HTMLResponse)
496
+ async def legacy_printer_status(request: Request, lab: str = "scan-results"):
497
+ """Legacy printer status page for a lab."""
498
+ zp = request.app.state.zp
499
+ templates = request.app.state.templates
500
+
501
+ if lab not in zp.printers.get("labs", {}):
502
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
503
+
504
+ printers = []
505
+ for name, info in zp.printers["labs"][lab].items():
506
+ printer_data = {
507
+ "name": name,
508
+ "ip_address": info.get("ip_address", ""),
509
+ "model": info.get("model", ""),
510
+ "serial": info.get("serial", ""),
511
+ "label_zpl_styles": info.get("label_zpl_styles", []),
512
+ "arp_data": info.get("arp_data", ""),
513
+ "status": "unknown",
514
+ }
515
+
516
+ # Try to get printer status (curl check)
517
+ if info.get("ip_address") and info["ip_address"] != "dl_png":
518
+ try:
519
+ result = subprocess.run(
520
+ ["curl", "-m", "4", info["ip_address"]],
521
+ capture_output=True,
522
+ text=True,
523
+ timeout=5,
524
+ )
525
+ for line in result.stdout.splitlines():
526
+ if "Status:" in line:
527
+ printer_data["status"] = line.strip()
528
+ break
529
+ except Exception:
530
+ printer_data["status"] = "Unable to connect"
531
+
532
+ printers.append(printer_data)
533
+
534
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
535
+
536
+ context = get_template_context(
537
+ request,
538
+ title=f"Printer Status - {lab}",
539
+ lab=lab,
540
+ printers=printers,
541
+ ip_root=ip_root,
542
+ labs=list(zp.printers.get("labs", {}).keys()),
543
+ )
544
+ return templates.TemplateResponse("legacy/printer_status.html", context)
545
+
546
+
547
+ @router.get("/legacy/simple_print_request", response_class=HTMLResponse)
548
+ async def legacy_simple_print_request(request: Request):
549
+ """Legacy simple print request form."""
550
+ zp = request.app.state.zp
551
+ templates = request.app.state.templates
552
+
553
+ pkg_path = request.app.state.pkg_path
554
+ styles_dir = pkg_path / "etc" / "label_styles"
555
+
556
+ template_names = []
557
+ if styles_dir.exists():
558
+ for f in sorted(styles_dir.iterdir()):
559
+ if f.is_file() and f.suffix == ".zpl":
560
+ template_names.append(f.stem)
561
+
562
+ labs_and_printers = {
563
+ lab: list(printers.keys())
564
+ for lab, printers in zp.printers.get("labs", {}).items()
565
+ }
566
+
567
+ context = get_template_context(
568
+ request,
569
+ title="Print Label",
570
+ templates=template_names,
571
+ labs=list(zp.printers.get("labs", {}).keys()),
572
+ labs_and_printers=json.dumps(labs_and_printers),
573
+ )
574
+ return templates.TemplateResponse("legacy/simple_print.html", context)
575
+
576
+
577
+ @router.get("/legacy/edit_zpl", response_class=HTMLResponse)
578
+ async def legacy_edit_zpl(request: Request):
579
+ """Legacy list ZPL templates for editing."""
580
+ templates = request.app.state.templates
581
+ pkg_path = request.app.state.pkg_path
582
+ styles_dir = pkg_path / "etc" / "label_styles"
583
+
584
+ stable_templates = []
585
+ draft_templates = []
586
+
587
+ if styles_dir.exists():
588
+ for f in sorted(styles_dir.iterdir()):
589
+ if f.is_file() and f.suffix == ".zpl":
590
+ stable_templates.append(f.name)
591
+
592
+ tmps_dir = styles_dir / "tmps"
593
+ if tmps_dir.exists():
594
+ for f in sorted(tmps_dir.iterdir()):
595
+ if f.is_file() and f.suffix == ".zpl":
596
+ draft_templates.append(f.name)
597
+
598
+ context = get_template_context(
599
+ request,
600
+ title="Edit ZPL Templates",
601
+ stable_templates=stable_templates,
602
+ draft_templates=draft_templates,
603
+ )
604
+ return templates.TemplateResponse("legacy/edit_zpl.html", context)
605
+
606
+
607
+ @router.get("/legacy/chg_ui_style", response_class=HTMLResponse)
608
+ async def legacy_chg_ui_style(request: Request, css_file: Optional[str] = None):
609
+ """Legacy change UI style or show available styles."""
610
+ if css_file:
611
+ request.app.state.css_theme = css_file
612
+ return RedirectResponse(url="/legacy", status_code=303)
613
+
614
+ templates = request.app.state.templates
615
+ pkg_path = request.app.state.pkg_path
616
+ static_dir = pkg_path / "static"
617
+
618
+ css_files = []
619
+ if static_dir.exists():
620
+ for f in sorted(static_dir.iterdir()):
621
+ if f.suffix == ".css":
622
+ css_files.append(f.name)
623
+
624
+ context = get_template_context(request, title="Change UI Style", css_files=css_files)
625
+ return templates.TemplateResponse("legacy/chg_ui_style.html", context)
626
+
627
+
628
+ @router.get("/legacy/printer_details", response_class=HTMLResponse)
629
+ async def legacy_printer_details(request: Request, printer_name: str, lab: str):
630
+ """Legacy show detailed printer information."""
631
+ zp = request.app.state.zp
632
+ templates = request.app.state.templates
633
+
634
+ if lab not in zp.printers.get("labs", {}):
635
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
636
+ if printer_name not in zp.printers["labs"][lab]:
637
+ raise HTTPException(status_code=404, detail=f"Printer '{printer_name}' not found")
638
+
639
+ printer_info = zp.printers["labs"][lab][printer_name]
640
+
641
+ # Try to get printer configuration
642
+ printer_config = ""
643
+ ip_addr = printer_info.get("ip_address", "")
644
+ if ip_addr and ip_addr != "dl_png":
645
+ try:
646
+ printer_config = zdcm.ZebraPrinter(ip_addr).get_configuration()
647
+ except Exception as e:
648
+ printer_config = f"Unable to retrieve config: {e}"
649
+
650
+ context = get_template_context(
651
+ request,
652
+ title=f"Printer Details - {printer_name}",
653
+ printer_name=printer_name,
654
+ lab=lab,
655
+ printer_info=printer_info,
656
+ printer_config=printer_config,
657
+ )
658
+ return templates.TemplateResponse("legacy/printer_details.html", context)
659
+
660
+
661
+ @router.get("/legacy/view_pstation_json", response_class=HTMLResponse)
662
+ async def legacy_view_pstation_json(request: Request, error_msg: Optional[str] = None):
663
+ """Legacy view and edit printer configuration JSON."""
664
+ zp = request.app.state.zp
665
+ templates = request.app.state.templates
666
+
667
+ config_data = json.dumps(zp.printers, indent=4)
668
+
669
+ context = get_template_context(
670
+ request,
671
+ title="View Printer Config JSON",
672
+ config_data=config_data,
673
+ error_msg=error_msg,
674
+ )
675
+ return templates.TemplateResponse("legacy/view_pstation_json.html", context)
676
+
677
+
678
+ @router.post("/legacy/save_pstation_json")
679
+ async def legacy_save_pstation_json(request: Request, json_data: str = Form(...)):
680
+ """Legacy save edited printer configuration JSON."""
681
+ zp = request.app.state.zp
682
+
683
+ try:
684
+ data = json.loads(json_data)
685
+ except json.JSONDecodeError as e:
686
+ return RedirectResponse(
687
+ url=f"/legacy/view_pstation_json?error_msg=Invalid+JSON:+{str(e)}",
688
+ status_code=303,
689
+ )
690
+
691
+ try:
692
+ # Backup and save
693
+ zp.save_printer_json()
694
+
695
+ # Write new config
696
+ with open(zp.printers_filename, "w") as f:
697
+ json.dump(data, f, indent=4)
698
+
699
+ # Reload config
700
+ zp.load_printer_json(json_file=zp.printers_filename, relative=False)
701
+
702
+ return RedirectResponse(url="/legacy", status_code=303)
703
+
704
+ except Exception as e:
705
+ return RedirectResponse(
706
+ url=f"/legacy/view_pstation_json?error_msg=Error+saving:+{str(e)}",
707
+ status_code=303,
708
+ )
709
+
710
+
711
+ @router.get("/legacy/clear_printers_json")
712
+ async def legacy_clear_printers_json(request: Request):
713
+ """Legacy clear the printer configuration JSON."""
714
+ zp = request.app.state.zp
715
+ zp.clear_printers_json()
716
+ time.sleep(1.2)
717
+ return RedirectResponse(url="/legacy", status_code=303)
718
+
719
+
720
+ @router.get("/legacy/reset_pstation_json")
721
+ async def legacy_reset_pstation_json(request: Request):
722
+ """Legacy reset printer config from template."""
723
+ zp = request.app.state.zp
724
+ zp.replace_printer_json_from_template()
725
+ time.sleep(1.2)
726
+ return RedirectResponse(url="/legacy", status_code=303)
727
+
728
+
729
+ @router.get("/legacy/list_prior_printer_config_files", response_class=HTMLResponse)
730
+ async def legacy_list_prior_printer_config_files(request: Request):
731
+ """Legacy list backed up printer config files."""
732
+ templates = request.app.state.templates
733
+ pkg_path = request.app.state.pkg_path
734
+ bkup_dir = pkg_path / "etc" / "old_printer_config"
735
+
736
+ backup_files = []
737
+ if bkup_dir.exists():
738
+ for f in sorted(bkup_dir.iterdir(), reverse=True):
739
+ if f.is_file():
740
+ backup_files.append(f.name)
741
+
742
+ context = get_template_context(
743
+ request,
744
+ title="Prior Printer Config Files",
745
+ backup_files=backup_files,
746
+ )
747
+ return templates.TemplateResponse("legacy/list_prior_configs.html", context)
748
+
749
+
750
+ @router.get("/legacy/build_new_printers_config_json", response_class=HTMLResponse)
751
+ async def legacy_build_new_printers_config_json(request: Request):
752
+ """Legacy show network scan form."""
753
+ zp = request.app.state.zp
754
+ templates = request.app.state.templates
755
+
756
+ ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
757
+
758
+ context = get_template_context(
759
+ request,
760
+ title="Scan Network for Printers",
761
+ ip_root=ip_root,
762
+ labs=list(zp.printers.get("labs", {}).keys()),
763
+ )
764
+ return templates.TemplateResponse("legacy/build_new_config.html", context)
765
+
766
+
767
+ @router.get("/legacy/probe_zebra_printers_add_to_printers_json")
768
+ async def legacy_probe_zebra_printers(
769
+ request: Request,
770
+ ip_stub: str = "192.168.1",
771
+ scan_wait: str = "0.25",
772
+ lab: str = "scan-results",
773
+ ):
774
+ """Legacy probe network for Zebra printers and add to config."""
775
+ zp = request.app.state.zp
776
+ zp.probe_zebra_printers_add_to_printers_json(
777
+ ip_stub=ip_stub, scan_wait=scan_wait, lab=lab
778
+ )
779
+ time.sleep(2.2)
780
+ return RedirectResponse(url=f"/legacy/printer_status?lab={lab}", status_code=303)
781
+
782
+
783
+ @router.get("/legacy/bpr", response_class=HTMLResponse)
784
+ async def legacy_bpr(request: Request):
785
+ """Legacy build print request - select lab, printer, template."""
786
+ zp = request.app.state.zp
787
+ templates = request.app.state.templates
788
+ pkg_path = request.app.state.pkg_path
789
+
790
+ styles_dir = pkg_path / "etc" / "label_styles"
791
+
792
+ stable_templates = []
793
+ draft_templates = []
794
+
795
+ if styles_dir.exists():
796
+ for f in sorted(styles_dir.iterdir()):
797
+ if f.is_file() and f.suffix == ".zpl":
798
+ stable_templates.append(f.stem)
799
+
800
+ tmps_dir = styles_dir / "tmps"
801
+ if tmps_dir.exists():
802
+ for f in sorted(tmps_dir.iterdir()):
803
+ if f.is_file() and f.suffix == ".zpl":
804
+ draft_templates.append(f.stem)
805
+
806
+ labs_dict = zp.printers.get("labs", {})
807
+
808
+ context = get_template_context(
809
+ request,
810
+ title="Build Print Request",
811
+ labs=list(labs_dict.keys()),
812
+ labs_dict=json.dumps(labs_dict),
813
+ stable_templates=stable_templates,
814
+ draft_templates=draft_templates,
815
+ )
816
+ return templates.TemplateResponse("legacy/bpr.html", context)
817
+
818
+
819
+ @router.get("/legacy/send_print_request", response_class=HTMLResponse)
820
+ async def legacy_send_print_request(request: Request):
821
+ """Legacy send print request - stable templates only."""
822
+ zp = request.app.state.zp
823
+ templates = request.app.state.templates
824
+
825
+ context = get_template_context(
826
+ request,
827
+ title="Send Print Request",
828
+ labs=zp.printers.get("labs", {}),
829
+ )
830
+ return templates.TemplateResponse("legacy/send_print_request.html", context)
831
+
832
+
833
+ @router.get("/legacy/build_print_request", response_class=HTMLResponse)
834
+ async def legacy_build_print_request(
835
+ request: Request,
836
+ lab: str = "",
837
+ printer: str = "",
838
+ printer_ip: str = "",
839
+ label_zpl_style: str = "",
840
+ filename: str = "",
841
+ ):
842
+ """Legacy show print request form with pre-filled values."""
843
+ templates = request.app.state.templates
844
+
845
+ if label_zpl_style in ["", "None"] and filename not in ["", "None"]:
846
+ label_zpl_style = filename.replace(".zpl", "")
847
+
848
+ context = get_template_context(
849
+ request,
850
+ title="Build Print Request",
851
+ lab=lab,
852
+ printer=printer,
853
+ printer_ip=printer_ip,
854
+ label_zpl_style=label_zpl_style,
855
+ )
856
+ return templates.TemplateResponse("legacy/build_print_request.html", context)
857
+
858
+
859
+ @router.get("/legacy/_print_label", response_class=HTMLResponse)
860
+ async def legacy_print_label(
861
+ request: Request,
862
+ lab: Optional[str] = None,
863
+ printer: str = "",
864
+ printer_ip: str = "",
865
+ label_zpl_style: str = "",
866
+ uid_barcode: str = "",
867
+ alt_a: str = "",
868
+ alt_b: str = "",
869
+ alt_c: str = "",
870
+ alt_d: str = "",
871
+ alt_e: str = "",
872
+ alt_f: str = "",
873
+ labSelect: str = "",
874
+ ):
875
+ """Legacy execute print request."""
876
+ zp = request.app.state.zp
877
+ templates = request.app.state.templates
878
+ rate_limiter = request.app.state.print_rate_limiter
879
+
880
+ if lab is None:
881
+ lab = labSelect
882
+
883
+ client_ip = request.client.host if request.client else "unknown"
884
+
885
+ # Check rate limit
886
+ allowed, reason = await rate_limiter.acquire(client_ip)
887
+ if not allowed:
888
+ raise HTTPException(status_code=429, detail=reason)
889
+
890
+ try:
891
+ result = zp.print_zpl(
892
+ lab=lab,
893
+ printer_name=printer,
894
+ label_zpl_style=label_zpl_style,
895
+ uid_barcode=uid_barcode,
896
+ alt_a=alt_a,
897
+ alt_b=alt_b,
898
+ alt_c=alt_c,
899
+ alt_d=alt_d,
900
+ alt_e=alt_e,
901
+ alt_f=alt_f,
902
+ client_ip=client_ip,
903
+ )
904
+ finally:
905
+ rate_limiter.release()
906
+
907
+ # Build the full URL for reference
908
+ full_url = str(request.url)
909
+
910
+ png_url = None
911
+ if result and ".png" in str(result):
912
+ png_name = str(result).split("/")[-1]
913
+ png_url = f"/files/{png_name}"
914
+
915
+ context = get_template_context(
916
+ request,
917
+ title="Print Result",
918
+ success=True,
919
+ full_url=full_url,
920
+ png_url=png_url,
921
+ )
922
+ return templates.TemplateResponse("legacy/print_result.html", context)
923
+
924
+
925
+ @router.get("/legacy/edit", response_class=HTMLResponse)
926
+ async def legacy_edit_template(
927
+ request: Request,
928
+ filename: str,
929
+ dtype: str = "",
930
+ ):
931
+ """Legacy edit a ZPL template file."""
932
+ zp = request.app.state.zp
933
+ templates = request.app.state.templates
934
+ pkg_path = request.app.state.pkg_path
935
+
936
+ if dtype:
937
+ filepath = pkg_path / "etc" / "label_styles" / dtype / filename
938
+ else:
939
+ filepath = pkg_path / "etc" / "label_styles" / filename
940
+
941
+ if not filepath.exists():
942
+ raise HTTPException(status_code=404, detail=f"Template '{filename}' not found")
943
+
944
+ content = filepath.read_text()
945
+
946
+ labs_dict = zp.printers.get("labs", {})
947
+
948
+ context = get_template_context(
949
+ request,
950
+ title=f"Edit: {filename}",
951
+ filename=filename,
952
+ content=content,
953
+ dtype=dtype,
954
+ labs=list(labs_dict.keys()),
955
+ labs_dict=json.dumps(labs_dict),
956
+ )
957
+ return templates.TemplateResponse("legacy/edit_template.html", context)
958
+
959
+
960
+ @router.post("/legacy/save", response_class=HTMLResponse)
961
+ async def legacy_save_template(
962
+ request: Request,
963
+ filename: str = Form(...),
964
+ content: str = Form(...),
965
+ ftag: str = Form("na"),
966
+ lab: str = Form(""),
967
+ printer: str = Form(""),
968
+ ):
969
+ """Legacy save ZPL template as a new draft file."""
970
+ templates = request.app.state.templates
971
+ pkg_path = request.app.state.pkg_path
972
+
973
+ rec_date = str(datetime.now()).replace(" ", "_")
974
+ new_filename = filename.replace(".zpl", f".{ftag}.{rec_date}.zpl")
975
+
976
+ tmps_dir = pkg_path / "etc" / "label_styles" / "tmps"
977
+ tmps_dir.mkdir(parents=True, exist_ok=True)
978
+
979
+ temp_filepath = tmps_dir / new_filename
980
+ temp_filepath.write_text(content)
981
+
982
+ context = get_template_context(
983
+ request,
984
+ title="Template Saved",
985
+ new_filename=new_filename,
986
+ )
987
+ return templates.TemplateResponse("legacy/save_result.html", context)
988
+
989
+
990
+ @router.post("/legacy/png_renderer")
991
+ async def legacy_png_renderer(
992
+ request: Request,
993
+ filename: str = Form(...),
994
+ content: str = Form(...),
995
+ lab: str = Form(""),
996
+ printer: str = Form(""),
997
+ ftag: str = Form(""),
998
+ ):
999
+ """Legacy render ZPL content to PNG."""
1000
+ zp = request.app.state.zp
1001
+ pkg_path = request.app.state.pkg_path
1002
+
1003
+ files_dir = pkg_path / "files"
1004
+ files_dir.mkdir(parents=True, exist_ok=True)
1005
+
1006
+ png_tmp_f = tempfile.NamedTemporaryFile(
1007
+ suffix=".png", dir=str(files_dir), delete=False
1008
+ ).name
1009
+
1010
+ zp.generate_label_png(content, png_fn=png_tmp_f)
1011
+
1012
+ # Return just the relative path for the img src
1013
+ return Response(
1014
+ content=f"files/{Path(png_tmp_f).name}",
1015
+ media_type="text/plain",
1016
+ )
1017
+
1018
+
1019
+ @router.post("/legacy/build_and_send_raw_print_request")
1020
+ async def legacy_build_and_send_raw_print_request(
1021
+ request: Request,
1022
+ lab: str = Form(...),
1023
+ printer: str = Form(...),
1024
+ content: str = Form(...),
1025
+ printer_ip: str = Form(""),
1026
+ label_zpl_style: str = Form(""),
1027
+ filename: str = Form(""),
1028
+ ftag: str = Form(""),
1029
+ ):
1030
+ """Legacy send raw ZPL content to printer."""
1031
+ zp = request.app.state.zp
1032
+ rate_limiter = request.app.state.print_rate_limiter
1033
+ client_ip = request.client.host if request.client else "unknown"
1034
+
1035
+ # Check rate limit
1036
+ allowed, reason = await rate_limiter.acquire(client_ip)
1037
+ if not allowed:
1038
+ raise HTTPException(status_code=429, detail=reason)
1039
+
1040
+ try:
1041
+ zp.print_zpl(
1042
+ lab=lab,
1043
+ printer_name=printer,
1044
+ label_zpl_style=None,
1045
+ zpl_content=content,
1046
+ client_ip=client_ip,
1047
+ )
1048
+ finally:
1049
+ rate_limiter.release()
1050
+
1051
+ return {"status": "sent"}