zebra-day 2.0.0__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.
- zebra_day/__init__.py +7 -2
- zebra_day/_version.py +1 -0
- zebra_day/cli/__init__.py +80 -30
- zebra_day/cli/cognito.py +15 -9
- zebra_day/cli/gui.py +21 -16
- zebra_day/cli/printer.py +34 -27
- zebra_day/cli/template.py +19 -15
- zebra_day/cmd_mgr.py +3 -6
- zebra_day/docs/gx420d-gx430d-ug-en.pdf +0 -0
- zebra_day/docs/hardware_config_guide.md +149 -0
- zebra_day/docs/programatic_guide.md +181 -0
- zebra_day/docs/qln420_zebra_manual.pdf +0 -0
- zebra_day/docs/uid_screed_light.md +38 -0
- zebra_day/docs/zd620-zd420-ug-en.pdf +0 -0
- zebra_day/docs/zebra_day_ui_guide.md +194 -0
- zebra_day/etc/printer_config.json +7 -1
- zebra_day/etc/printer_config.template.json +3 -17
- zebra_day/etc/tmp_printers139.json +10 -0
- zebra_day/etc/tmp_printers147.json +10 -0
- zebra_day/etc/tmp_printers34.json +10 -0
- zebra_day/etc/tmp_printers389.json +10 -0
- zebra_day/etc/tmp_printers398.json +10 -0
- zebra_day/etc/tmp_printers437.json +10 -0
- zebra_day/etc/tmp_printers439.json +10 -0
- zebra_day/etc/tmp_printers440.json +10 -0
- zebra_day/etc/tmp_printers508.json +10 -0
- zebra_day/etc/tmp_printers543.json +10 -0
- zebra_day/etc/tmp_printers835.json +10 -0
- zebra_day/etc/tmp_printers842.json +10 -0
- zebra_day/etc/tmp_printers931.json +10 -0
- zebra_day/etc/tmp_printers969.json +10 -0
- zebra_day/exceptions.py +1 -1
- zebra_day/files/corners_smallTube_preview.png +0 -0
- zebra_day/files/test_png_2897.png +0 -0
- zebra_day/files/test_png_31690.png +0 -0
- zebra_day/files/test_png_33804.png +0 -0
- zebra_day/files/test_png_34737.png +0 -0
- zebra_day/files/test_png_4161.png +0 -0
- zebra_day/files/test_png_44748.png +0 -0
- zebra_day/files/test_png_4635.png +0 -0
- zebra_day/files/test_png_56349.png +0 -0
- zebra_day/files/test_png_5936.png +0 -0
- zebra_day/files/test_png_64110.png +0 -0
- zebra_day/files/test_png_64891.png +0 -0
- zebra_day/files/test_png_69002.png +0 -0
- zebra_day/files/test_png_70065.png +0 -0
- zebra_day/files/test_png_72366.png +0 -0
- zebra_day/files/test_png_77793.png +0 -0
- zebra_day/files/test_png_9572.png +0 -0
- zebra_day/imgs/.hold +0 -0
- zebra_day/imgs/bar_ltpurp.png +0 -0
- zebra_day/imgs/bar_purp.png +0 -0
- zebra_day/imgs/bar_purp3.png +0 -0
- zebra_day/imgs/bar_red.png +0 -0
- zebra_day/imgs/legacy/UBC_gantt_chart.png +0 -0
- zebra_day/imgs/legacy/gx420d_network_config.png +0 -0
- zebra_day/imgs/legacy/gx420d_printer_config.png +0 -0
- zebra_day/imgs/legacy/ngrok.png +0 -0
- zebra_day/imgs/legacy/printer_details.png +0 -0
- zebra_day/imgs/legacy/quick_start_test_label.png +0 -0
- zebra_day/imgs/legacy/quick_start_test_label2.png +0 -0
- zebra_day/imgs/legacy/zd620_network_config.png +0 -0
- zebra_day/imgs/legacy/zd620_printer_config.png +0 -0
- zebra_day/imgs/legacy/zday_quick_gui.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_dog.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_flower.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_main.png +0 -0
- zebra_day/imgs/legacy/zebra_day_available_zpl_templates.png +0 -0
- zebra_day/imgs/legacy/zebra_day_bkup_pconfig.png +0 -0
- zebra_day/imgs/legacy/zebra_day_home.png +0 -0
- zebra_day/imgs/legacy/zebra_day_manual_print.png +0 -0
- zebra_day/imgs/legacy/zebra_day_printer_fleet_json.png +0 -0
- zebra_day/imgs/legacy/zebra_day_quick_ex.png +0 -0
- zebra_day/imgs/legacy/zebra_day_zpl_template_IRLa.png +0 -0
- zebra_day/imgs/legacy/zebra_day_zpl_template_IRLb.png +0 -0
- zebra_day/imgs/ui_api_docs.png +0 -0
- zebra_day/imgs/ui_config.png +0 -0
- zebra_day/imgs/ui_dashboard.png +0 -0
- zebra_day/imgs/ui_print_request.png +0 -0
- zebra_day/imgs/ui_printers.png +0 -0
- zebra_day/imgs/ui_templates.png +0 -0
- zebra_day/logging_config.py +4 -9
- zebra_day/mkcert.py +157 -0
- zebra_day/paths.py +1 -2
- zebra_day/print_mgr.py +165 -145
- zebra_day/templates/modern/config.html +7 -0
- zebra_day/templates/modern/print_request.html +61 -3
- zebra_day/web/__init__.py +1 -1
- zebra_day/web/app.py +21 -16
- zebra_day/web/auth.py +17 -15
- zebra_day/web/middleware.py +8 -5
- zebra_day/web/routers/__init__.py +0 -1
- zebra_day/web/routers/api.py +192 -43
- zebra_day/web/routers/ui.py +31 -33
- zebra_day/zpl_renderer.py +45 -34
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/METADATA +76 -67
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/RECORD +101 -29
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/WHEEL +0 -0
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/entry_points.txt +0 -0
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/licenses/LICENSE +0 -0
- {zebra_day-2.0.0.dist-info → zebra_day-2.1.4.dist-info}/top_level.txt +0 -0
zebra_day/web/routers/api.py
CHANGED
|
@@ -4,23 +4,30 @@ Versioned JSON API router for zebra_day.
|
|
|
4
4
|
Provides programmatic access to printer management and label printing.
|
|
5
5
|
All endpoints return JSON and are prefixed with /api/v1/.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
|
-
from
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any
|
|
10
12
|
|
|
11
13
|
from fastapi import APIRouter, HTTPException, Request
|
|
14
|
+
from fastapi.responses import FileResponse
|
|
12
15
|
from pydantic import BaseModel, Field
|
|
13
16
|
|
|
17
|
+
from zebra_day import paths as xdg
|
|
18
|
+
|
|
14
19
|
router = APIRouter()
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
# ----- Request/Response Models -----
|
|
18
23
|
|
|
24
|
+
|
|
19
25
|
class PrintRequest(BaseModel):
|
|
20
26
|
"""Request model for printing a label."""
|
|
27
|
+
|
|
21
28
|
lab: str = Field(..., description="Lab identifier")
|
|
22
29
|
printer: str = Field(..., description="Printer name")
|
|
23
|
-
label_zpl_style:
|
|
30
|
+
label_zpl_style: str | None = Field(None, description="ZPL template name")
|
|
24
31
|
uid_barcode: str = Field("", description="UID for barcode")
|
|
25
32
|
alt_a: str = Field("", description="Alternative field A")
|
|
26
33
|
alt_b: str = Field("", description="Alternative field B")
|
|
@@ -33,44 +40,77 @@ class PrintRequest(BaseModel):
|
|
|
33
40
|
|
|
34
41
|
class PrintResponse(BaseModel):
|
|
35
42
|
"""Response model for print request."""
|
|
43
|
+
|
|
36
44
|
success: bool
|
|
37
45
|
message: str
|
|
38
|
-
png_url:
|
|
46
|
+
png_url: str | None = None
|
|
39
47
|
|
|
40
48
|
|
|
41
49
|
class PrinterInfo(BaseModel):
|
|
42
50
|
"""Printer information model (v2.0.0 schema)."""
|
|
51
|
+
|
|
43
52
|
id: str = Field(..., description="Printer identifier/key in JSON")
|
|
44
53
|
ip_address: str
|
|
45
|
-
printer_name:
|
|
46
|
-
lab_location:
|
|
54
|
+
printer_name: str | None = Field(None, description="User-friendly display name")
|
|
55
|
+
lab_location: str | None = Field(None, description="Location within the lab")
|
|
47
56
|
manufacturer: str = Field("zebra", description="Printer manufacturer")
|
|
48
57
|
model: str
|
|
49
58
|
serial: str
|
|
50
|
-
label_zpl_styles:
|
|
51
|
-
default_label_style:
|
|
59
|
+
label_zpl_styles: list[str]
|
|
60
|
+
default_label_style: str | None = Field(
|
|
61
|
+
None, description="Default label style to use when none specified"
|
|
62
|
+
)
|
|
52
63
|
print_method: str
|
|
53
|
-
notes:
|
|
64
|
+
notes: str | None = Field("", description="Optional notes")
|
|
54
65
|
|
|
55
66
|
|
|
56
67
|
class LabInfo(BaseModel):
|
|
57
68
|
"""Lab information model (v2.0.0 schema)."""
|
|
69
|
+
|
|
58
70
|
id: str = Field(..., description="Lab identifier/key in JSON")
|
|
59
71
|
lab_name: str = Field(..., description="Human-readable lab name")
|
|
60
|
-
available_locations:
|
|
61
|
-
|
|
72
|
+
available_locations: list[str] = Field(
|
|
73
|
+
default_factory=list, description="Valid location options for printers"
|
|
74
|
+
)
|
|
75
|
+
printers: list[PrinterInfo]
|
|
62
76
|
|
|
63
77
|
|
|
64
78
|
class LabPrinters(BaseModel):
|
|
65
79
|
"""Lab and its printers (deprecated, use LabInfo)."""
|
|
80
|
+
|
|
66
81
|
lab: str
|
|
67
|
-
printers:
|
|
82
|
+
printers: list[PrinterInfo]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class RenderRequest(BaseModel):
|
|
86
|
+
"""Request model for rendering ZPL to PNG."""
|
|
87
|
+
|
|
88
|
+
template: str | None = Field(None, description="ZPL template name (e.g., 'tube_2inX1in')")
|
|
89
|
+
zpl_content: str | None = Field(
|
|
90
|
+
None, description="Raw ZPL content (takes precedence over template)"
|
|
91
|
+
)
|
|
92
|
+
uid_barcode: str = Field("", description="UID for barcode")
|
|
93
|
+
alt_a: str = Field("", description="Alternative field A")
|
|
94
|
+
alt_b: str = Field("", description="Alternative field B")
|
|
95
|
+
alt_c: str = Field("", description="Alternative field C")
|
|
96
|
+
alt_d: str = Field("", description="Alternative field D")
|
|
97
|
+
alt_e: str = Field("", description="Alternative field E")
|
|
98
|
+
alt_f: str = Field("", description="Alternative field F")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RenderResponse(BaseModel):
|
|
102
|
+
"""Response model for render request (when not returning PNG directly)."""
|
|
103
|
+
|
|
104
|
+
success: bool
|
|
105
|
+
message: str
|
|
106
|
+
png_url: str = Field(..., description="URL to download the generated PNG")
|
|
68
107
|
|
|
69
108
|
|
|
70
109
|
# ----- Endpoints -----
|
|
71
110
|
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
|
|
112
|
+
@router.get("/labs", response_model=list[str])
|
|
113
|
+
async def list_labs(request: Request) -> list[str]:
|
|
74
114
|
"""List all available labs."""
|
|
75
115
|
zp = request.app.state.zp
|
|
76
116
|
return list(zp.printers.get("labs", {}).keys())
|
|
@@ -114,8 +154,8 @@ async def get_lab(request: Request, lab: str) -> LabInfo:
|
|
|
114
154
|
)
|
|
115
155
|
|
|
116
156
|
|
|
117
|
-
@router.get("/labs/{lab}/printers", response_model=
|
|
118
|
-
async def list_printers(request: Request, lab: str) ->
|
|
157
|
+
@router.get("/labs/{lab}/printers", response_model=list[PrinterInfo])
|
|
158
|
+
async def list_printers(request: Request, lab: str) -> list[PrinterInfo]:
|
|
119
159
|
"""List all printers in a lab."""
|
|
120
160
|
zp = request.app.state.zp
|
|
121
161
|
labs = zp.printers.get("labs", {})
|
|
@@ -146,10 +186,9 @@ async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
|
|
|
146
186
|
return printers
|
|
147
187
|
|
|
148
188
|
|
|
149
|
-
@router.get("/templates", response_model=
|
|
150
|
-
async def list_templates(request: Request) ->
|
|
189
|
+
@router.get("/templates", response_model=list[str])
|
|
190
|
+
async def list_templates(request: Request) -> list[str]:
|
|
151
191
|
"""List all available ZPL templates."""
|
|
152
|
-
from pathlib import Path
|
|
153
192
|
|
|
154
193
|
pkg_path = request.app.state.pkg_path
|
|
155
194
|
styles_dir = pkg_path / "etc" / "label_styles"
|
|
@@ -183,7 +222,7 @@ async def print_label(request: Request, print_req: PrintRequest) -> PrintRespons
|
|
|
183
222
|
raise HTTPException(status_code=429, detail=reason)
|
|
184
223
|
|
|
185
224
|
try:
|
|
186
|
-
|
|
225
|
+
zp.print_zpl(
|
|
187
226
|
lab=print_req.lab,
|
|
188
227
|
printer_name=print_req.printer,
|
|
189
228
|
label_zpl_style=print_req.label_zpl_style,
|
|
@@ -197,45 +236,149 @@ async def print_label(request: Request, print_req: PrintRequest) -> PrintRespons
|
|
|
197
236
|
print_n=print_req.copies,
|
|
198
237
|
client_ip=client_ip,
|
|
199
238
|
)
|
|
200
|
-
|
|
201
|
-
# Check if result is a PNG file path
|
|
202
|
-
if result and ".png" in str(result):
|
|
203
|
-
png_name = str(result).split("/")[-1]
|
|
204
|
-
return PrintResponse(
|
|
205
|
-
success=True,
|
|
206
|
-
message="PNG generated successfully",
|
|
207
|
-
png_url=f"/files/{png_name}",
|
|
208
|
-
)
|
|
209
|
-
|
|
210
239
|
return PrintResponse(success=True, message="Print request sent successfully")
|
|
211
240
|
except Exception as e:
|
|
212
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
241
|
+
raise HTTPException(status_code=500, detail=str(e)) from None
|
|
213
242
|
finally:
|
|
214
243
|
rate_limiter.release()
|
|
215
244
|
|
|
216
245
|
|
|
246
|
+
@router.post("/render", response_model=RenderResponse)
|
|
247
|
+
async def render_label(request: Request, render_req: RenderRequest) -> RenderResponse:
|
|
248
|
+
"""
|
|
249
|
+
Render ZPL to PNG image.
|
|
250
|
+
|
|
251
|
+
This endpoint generates a PNG image from ZPL content without sending to a printer.
|
|
252
|
+
You can provide either:
|
|
253
|
+
- A template name (e.g., 'tube_2inX1in') with field values
|
|
254
|
+
- Raw ZPL content directly
|
|
255
|
+
|
|
256
|
+
Returns a URL to download the generated PNG.
|
|
257
|
+
"""
|
|
258
|
+
zp = request.app.state.zp
|
|
259
|
+
|
|
260
|
+
# Validate that we have either template or zpl_content
|
|
261
|
+
if not render_req.template and not render_req.zpl_content:
|
|
262
|
+
raise HTTPException(
|
|
263
|
+
status_code=400, detail="Either 'template' or 'zpl_content' must be provided"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
# Generate ZPL string from template if not provided directly
|
|
268
|
+
if render_req.zpl_content:
|
|
269
|
+
zpl_string = render_req.zpl_content
|
|
270
|
+
else:
|
|
271
|
+
zpl_string = zp.formulate_zpl(
|
|
272
|
+
uid_barcode=render_req.uid_barcode,
|
|
273
|
+
alt_a=render_req.alt_a,
|
|
274
|
+
alt_b=render_req.alt_b,
|
|
275
|
+
alt_c=render_req.alt_c,
|
|
276
|
+
alt_d=render_req.alt_d,
|
|
277
|
+
alt_e=render_req.alt_e,
|
|
278
|
+
alt_f=render_req.alt_f,
|
|
279
|
+
label_zpl_style=render_req.template,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Generate unique filename
|
|
283
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H:%M:%S.%f")
|
|
284
|
+
template_name = render_req.template or "custom"
|
|
285
|
+
png_filename = f"zpl_render_{template_name}_{timestamp}.png"
|
|
286
|
+
png_path = xdg.get_generated_files_dir() / png_filename
|
|
287
|
+
|
|
288
|
+
# Render to PNG
|
|
289
|
+
zp.generate_label_png(zpl_string, str(png_path), relative=False)
|
|
290
|
+
|
|
291
|
+
return RenderResponse(
|
|
292
|
+
success=True,
|
|
293
|
+
message="PNG rendered successfully",
|
|
294
|
+
png_url=f"/generated/{png_filename}",
|
|
295
|
+
)
|
|
296
|
+
except FileNotFoundError as e:
|
|
297
|
+
raise HTTPException(status_code=404, detail=f"Template not found: {e}") from None
|
|
298
|
+
except Exception as e:
|
|
299
|
+
raise HTTPException(status_code=500, detail=f"Render failed: {e}") from None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@router.post("/render/png")
|
|
303
|
+
async def render_label_png(request: Request, render_req: RenderRequest):
|
|
304
|
+
"""
|
|
305
|
+
Render ZPL to PNG and return the image directly.
|
|
306
|
+
|
|
307
|
+
Same as /render but returns the PNG file directly instead of a URL.
|
|
308
|
+
Useful for programmatic access where you want the image bytes.
|
|
309
|
+
"""
|
|
310
|
+
zp = request.app.state.zp
|
|
311
|
+
|
|
312
|
+
# Validate that we have either template or zpl_content
|
|
313
|
+
if not render_req.template and not render_req.zpl_content:
|
|
314
|
+
raise HTTPException(
|
|
315
|
+
status_code=400, detail="Either 'template' or 'zpl_content' must be provided"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
# Generate ZPL string from template if not provided directly
|
|
320
|
+
if render_req.zpl_content:
|
|
321
|
+
zpl_string = render_req.zpl_content
|
|
322
|
+
else:
|
|
323
|
+
zpl_string = zp.formulate_zpl(
|
|
324
|
+
uid_barcode=render_req.uid_barcode,
|
|
325
|
+
alt_a=render_req.alt_a,
|
|
326
|
+
alt_b=render_req.alt_b,
|
|
327
|
+
alt_c=render_req.alt_c,
|
|
328
|
+
alt_d=render_req.alt_d,
|
|
329
|
+
alt_e=render_req.alt_e,
|
|
330
|
+
alt_f=render_req.alt_f,
|
|
331
|
+
label_zpl_style=render_req.template,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Generate unique filename
|
|
335
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H:%M:%S.%f")
|
|
336
|
+
template_name = render_req.template or "custom"
|
|
337
|
+
png_filename = f"zpl_render_{template_name}_{timestamp}.png"
|
|
338
|
+
png_path = xdg.get_generated_files_dir() / png_filename
|
|
339
|
+
|
|
340
|
+
# Render to PNG
|
|
341
|
+
zp.generate_label_png(zpl_string, str(png_path), relative=False)
|
|
342
|
+
|
|
343
|
+
# Return the file directly
|
|
344
|
+
return FileResponse(
|
|
345
|
+
path=str(png_path),
|
|
346
|
+
media_type="image/png",
|
|
347
|
+
filename=png_filename,
|
|
348
|
+
)
|
|
349
|
+
except FileNotFoundError as e:
|
|
350
|
+
raise HTTPException(status_code=404, detail=f"Template not found: {e}") from None
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise HTTPException(status_code=500, detail=f"Render failed: {e}") from None
|
|
353
|
+
|
|
354
|
+
|
|
217
355
|
@router.get("/config")
|
|
218
|
-
async def get_config(request: Request) ->
|
|
356
|
+
async def get_config(request: Request) -> dict[str, Any]:
|
|
219
357
|
"""Get the current printer configuration."""
|
|
220
358
|
zp = request.app.state.zp
|
|
221
|
-
return zp.printers
|
|
359
|
+
return dict(zp.printers)
|
|
222
360
|
|
|
223
361
|
|
|
224
362
|
# ----- Lab Settings Endpoints -----
|
|
225
363
|
|
|
364
|
+
|
|
226
365
|
class LabUpdateRequest(BaseModel):
|
|
227
366
|
"""Request model for updating lab settings."""
|
|
228
|
-
|
|
229
|
-
|
|
367
|
+
|
|
368
|
+
lab_name: str | None = Field(None, description="Human-readable lab name")
|
|
369
|
+
available_locations: list[str] | None = Field(None, description="List of valid locations")
|
|
230
370
|
|
|
231
371
|
|
|
232
372
|
class PrinterUpdateRequest(BaseModel):
|
|
233
373
|
"""Request model for updating printer settings."""
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
374
|
+
|
|
375
|
+
printer_name: str | None = Field(None, description="User-friendly display name")
|
|
376
|
+
lab_location: str | None = Field(None, description="Location within the lab")
|
|
377
|
+
notes: str | None = Field(None, description="Optional notes")
|
|
378
|
+
label_zpl_styles: list[str] | None = Field(None, description="Allowed ZPL styles")
|
|
379
|
+
default_label_style: str | None = Field(
|
|
380
|
+
None, description="Default label style to use when none specified"
|
|
381
|
+
)
|
|
239
382
|
|
|
240
383
|
|
|
241
384
|
@router.patch("/labs/{lab}", response_model=LabInfo)
|
|
@@ -274,7 +417,9 @@ async def update_printer(
|
|
|
274
417
|
|
|
275
418
|
lab_printers = labs[lab].get("printers", {})
|
|
276
419
|
if printer_id not in lab_printers:
|
|
277
|
-
raise HTTPException(
|
|
420
|
+
raise HTTPException(
|
|
421
|
+
status_code=404, detail=f"Printer '{printer_id}' not found in lab '{lab}'"
|
|
422
|
+
)
|
|
278
423
|
|
|
279
424
|
printer_data = lab_printers[printer_id]
|
|
280
425
|
|
|
@@ -288,12 +433,16 @@ async def update_printer(
|
|
|
288
433
|
printer_data["label_zpl_styles"] = update.label_zpl_styles
|
|
289
434
|
if update.default_label_style is not None:
|
|
290
435
|
# Validate that the style exists in label_zpl_styles (if it's not empty string)
|
|
291
|
-
if update.default_label_style and update.default_label_style not in printer_data.get(
|
|
436
|
+
if update.default_label_style and update.default_label_style not in printer_data.get(
|
|
437
|
+
"label_zpl_styles", []
|
|
438
|
+
):
|
|
292
439
|
raise HTTPException(
|
|
293
440
|
status_code=400,
|
|
294
|
-
detail=f"Default label style '{update.default_label_style}' must be one of: {printer_data.get('label_zpl_styles', [])}"
|
|
441
|
+
detail=f"Default label style '{update.default_label_style}' must be one of: {printer_data.get('label_zpl_styles', [])}",
|
|
295
442
|
)
|
|
296
|
-
printer_data["default_label_style"] =
|
|
443
|
+
printer_data["default_label_style"] = (
|
|
444
|
+
update.default_label_style if update.default_label_style else None
|
|
445
|
+
)
|
|
297
446
|
|
|
298
447
|
# Save changes
|
|
299
448
|
zp.save_printer_json(zp.printers_filename, relative=False)
|
zebra_day/web/routers/ui.py
CHANGED
|
@@ -4,22 +4,20 @@ UI router for zebra_day web interface.
|
|
|
4
4
|
Provides HTML endpoints for the web-based management interface.
|
|
5
5
|
All routes use the modern UI design with responsive layouts.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
import json
|
|
10
|
-
import os
|
|
11
|
-
import subprocess
|
|
12
11
|
import tempfile
|
|
13
12
|
import time
|
|
14
13
|
from datetime import datetime
|
|
15
14
|
from pathlib import Path
|
|
16
|
-
from typing import Optional
|
|
17
15
|
|
|
18
|
-
from fastapi import APIRouter,
|
|
16
|
+
from fastapi import APIRouter, Form, HTTPException, Request
|
|
19
17
|
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
|
20
18
|
|
|
21
|
-
from zebra_day.logging_config import get_logger
|
|
22
19
|
import zebra_day.cmd_mgr as zdcm
|
|
20
|
+
from zebra_day.logging_config import get_logger
|
|
23
21
|
|
|
24
22
|
_log = get_logger(__name__)
|
|
25
23
|
|
|
@@ -92,6 +90,7 @@ def get_stats(zp, pkg_path: Path) -> dict:
|
|
|
92
90
|
# MODERN UI ROUTES (root level)
|
|
93
91
|
# =============================================================================
|
|
94
92
|
|
|
93
|
+
|
|
95
94
|
@router.get("/", response_class=HTMLResponse)
|
|
96
95
|
async def modern_dashboard(request: Request):
|
|
97
96
|
"""Modern dashboard - home page."""
|
|
@@ -145,19 +144,21 @@ async def modern_printers_by_lab(request: Request, lab: str):
|
|
|
145
144
|
|
|
146
145
|
printers = []
|
|
147
146
|
for name, info in lab_printers.items():
|
|
148
|
-
printers.append(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
147
|
+
printers.append(
|
|
148
|
+
{
|
|
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
|
+
)
|
|
161
162
|
|
|
162
163
|
ip_root = ".".join(request.app.state.local_ip.split(".")[:-1])
|
|
163
164
|
|
|
@@ -332,13 +333,13 @@ async def modern_template_preview(
|
|
|
332
333
|
template_name = filepath.stem
|
|
333
334
|
output_path = output_dir / f"{template_name}_preview.png"
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
zp.generate_label_png(zpl_content, str(output_path), False)
|
|
336
337
|
|
|
337
338
|
# Return redirect to the generated file
|
|
338
339
|
return RedirectResponse(url=f"/files/{template_name}_preview.png", status_code=303)
|
|
339
340
|
|
|
340
341
|
except Exception as e:
|
|
341
|
-
raise HTTPException(status_code=500, detail=f"Preview generation failed: {e}")
|
|
342
|
+
raise HTTPException(status_code=500, detail=f"Preview generation failed: {e}") from None
|
|
342
343
|
|
|
343
344
|
|
|
344
345
|
@router.get("/config", response_class=HTMLResponse)
|
|
@@ -360,12 +361,16 @@ async def modern_config(request: Request):
|
|
|
360
361
|
"backups": stats["backups"],
|
|
361
362
|
}
|
|
362
363
|
|
|
364
|
+
# Get the config file path that was loaded
|
|
365
|
+
config_file_path = getattr(zp, "printers_filename", "Unknown")
|
|
366
|
+
|
|
363
367
|
context = get_modern_context(
|
|
364
368
|
request,
|
|
365
369
|
active_page="config",
|
|
366
370
|
labs=labs,
|
|
367
371
|
ip_root=ip_root,
|
|
368
372
|
config_summary=config_summary,
|
|
373
|
+
config_file_path=config_file_path,
|
|
369
374
|
)
|
|
370
375
|
return templates.TemplateResponse("modern/config.html", context)
|
|
371
376
|
|
|
@@ -388,7 +393,7 @@ async def modern_config_view(request: Request):
|
|
|
388
393
|
|
|
389
394
|
|
|
390
395
|
@router.get("/config/edit", response_class=HTMLResponse)
|
|
391
|
-
async def modern_config_edit(request: Request, error_msg:
|
|
396
|
+
async def modern_config_edit(request: Request, error_msg: str | None = None):
|
|
392
397
|
"""Edit printer configuration JSON."""
|
|
393
398
|
zp = request.app.state.zp
|
|
394
399
|
templates = request.app.state.templates
|
|
@@ -433,10 +438,7 @@ async def modern_config_save(request: Request, json_data: str = Form(...)):
|
|
|
433
438
|
return RedirectResponse(url="/config", status_code=303)
|
|
434
439
|
|
|
435
440
|
except json.JSONDecodeError as e:
|
|
436
|
-
return RedirectResponse(
|
|
437
|
-
url=f"/config/edit?error_msg=Invalid JSON: {e}",
|
|
438
|
-
status_code=303
|
|
439
|
-
)
|
|
441
|
+
return RedirectResponse(url=f"/config/edit?error_msg=Invalid JSON: {e}", status_code=303)
|
|
440
442
|
|
|
441
443
|
|
|
442
444
|
@router.get("/config/backups", response_class=HTMLResponse)
|
|
@@ -504,9 +506,7 @@ async def modern_config_scan(
|
|
|
504
506
|
):
|
|
505
507
|
"""Scan network for printers."""
|
|
506
508
|
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
|
-
)
|
|
509
|
+
zp.probe_zebra_printers_add_to_printers_json(ip_stub=ip_stub, scan_wait=scan_wait, lab=lab)
|
|
510
510
|
time.sleep(2.2)
|
|
511
511
|
return RedirectResponse(url=f"/printers/{lab}", status_code=303)
|
|
512
512
|
|
|
@@ -514,7 +514,7 @@ async def modern_config_scan(
|
|
|
514
514
|
@router.get("/_print_label", response_class=HTMLResponse)
|
|
515
515
|
async def modern_print_label(
|
|
516
516
|
request: Request,
|
|
517
|
-
lab:
|
|
517
|
+
lab: str | None = None,
|
|
518
518
|
printer: str = "",
|
|
519
519
|
printer_ip: str = "",
|
|
520
520
|
label_zpl_style: str = "",
|
|
@@ -565,7 +565,7 @@ async def modern_print_label(
|
|
|
565
565
|
png_url = None
|
|
566
566
|
if result and ".png" in str(result):
|
|
567
567
|
png_name = str(result).split("/")[-1]
|
|
568
|
-
png_url = f"/
|
|
568
|
+
png_url = f"/generated/{png_name}"
|
|
569
569
|
|
|
570
570
|
context = get_modern_context(
|
|
571
571
|
request,
|
|
@@ -623,9 +623,7 @@ async def modern_png_renderer(
|
|
|
623
623
|
files_dir = pkg_path / "files"
|
|
624
624
|
files_dir.mkdir(parents=True, exist_ok=True)
|
|
625
625
|
|
|
626
|
-
png_tmp_f = tempfile.NamedTemporaryFile(
|
|
627
|
-
suffix=".png", dir=str(files_dir), delete=False
|
|
628
|
-
).name
|
|
626
|
+
png_tmp_f = tempfile.NamedTemporaryFile(suffix=".png", dir=str(files_dir), delete=False).name
|
|
629
627
|
|
|
630
628
|
zp.generate_label_png(content, png_fn=png_tmp_f)
|
|
631
629
|
|