zebra-day 1.0.2__py3-none-any.whl → 2.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +101 -13
- 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 +5 -11
- zebra_day/etc/printer_config.template.json +5 -11
- zebra_day/etc/tmp_printers120.json +10 -0
- zebra_day/etc/tmp_printers139.json +10 -0
- zebra_day/etc/tmp_printers145.json +10 -0
- zebra_day/etc/tmp_printers147.json +10 -0
- zebra_day/etc/tmp_printers207.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_printers469.json +10 -0
- zebra_day/etc/tmp_printers485.json +10 -0
- zebra_day/etc/tmp_printers508.json +10 -0
- zebra_day/etc/tmp_printers531.json +10 -0
- zebra_day/etc/tmp_printers540.json +10 -0
- zebra_day/etc/tmp_printers542.json +10 -0
- zebra_day/etc/tmp_printers543.json +10 -0
- zebra_day/etc/tmp_printers552.json +10 -0
- zebra_day/etc/tmp_printers715.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/etc/tmp_printers972.json +10 -0
- zebra_day/exceptions.py +1 -1
- zebra_day/files/blank_preview.png +0 -0
- zebra_day/files/corners_20cmX30cm_preview.png +0 -0
- zebra_day/files/corners_smallTube_preview.png +0 -0
- zebra_day/files/generic_2inX1in_preview.png +0 -0
- zebra_day/files/test_png_12020.png +0 -0
- zebra_day/files/test_png_12352.png +0 -0
- zebra_day/files/test_png_15472.png +0 -0
- zebra_day/files/test_png_24493.png +0 -0
- zebra_day/files/test_png_2897.png +0 -0
- zebra_day/files/test_png_30069.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_47791.png +0 -0
- zebra_day/files/test_png_47799.png +0 -0
- zebra_day/files/test_png_55588.png +0 -0
- zebra_day/files/test_png_56349.png +0 -0
- zebra_day/files/test_png_58809.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_67242.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_89893.png +0 -0
- zebra_day/files/test_png_9572.png +0 -0
- zebra_day/files/tube_20mmX30mmA_preview.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 +261 -185
- zebra_day/templates/modern/config.html +7 -0
- zebra_day/templates/modern/config_backups.html +59 -0
- zebra_day/templates/modern/config_editor.html +95 -0
- zebra_day/templates/modern/config_new.html +93 -0
- zebra_day/templates/modern/print_request.html +70 -8
- zebra_day/templates/modern/printer_detail.html +161 -34
- zebra_day/templates/modern/printers.html +17 -6
- zebra_day/templates/modern/template_editor.html +7 -4
- zebra_day/web/__init__.py +1 -1
- zebra_day/web/app.py +99 -17
- 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 +330 -31
- zebra_day/web/routers/ui.py +174 -591
- zebra_day/zpl_renderer.py +45 -34
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/METADATA +144 -74
- zebra_day-2.1.4.dist-info/RECORD +240 -0
- zebra_day/bin/fetch_zebra_config.py +0 -15
- zebra_day/bin/generate_coord_grid_zpl.py +0 -50
- zebra_day/bin/print_zpl_from_file.py +0 -21
- zebra_day/bin/probe_new_label_dimensions.py +0 -75
- zebra_day/bin/scan_for_networed_zebra_printers.py +0 -23
- zebra_day/bin/scan_for_networed_zebra_printers_arp_scan.sh +0 -1
- zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +0 -30
- zebra_day/bin/zserve.py +0 -1062
- zebra_day/templates/base.html +0 -36
- zebra_day/templates/bpr.html +0 -72
- zebra_day/templates/build_new_config.html +0 -36
- zebra_day/templates/build_print_request.html +0 -32
- zebra_day/templates/chg_ui_style.html +0 -19
- zebra_day/templates/edit_template.html +0 -128
- zebra_day/templates/edit_zpl.html +0 -37
- zebra_day/templates/index.html +0 -82
- zebra_day/templates/legacy/base.html +0 -37
- zebra_day/templates/legacy/bpr.html +0 -72
- zebra_day/templates/legacy/build_new_config.html +0 -36
- zebra_day/templates/legacy/build_print_request.html +0 -32
- zebra_day/templates/legacy/chg_ui_style.html +0 -19
- zebra_day/templates/legacy/edit_template.html +0 -128
- zebra_day/templates/legacy/edit_zpl.html +0 -37
- zebra_day/templates/legacy/index.html +0 -82
- zebra_day/templates/legacy/list_prior_configs.html +0 -24
- zebra_day/templates/legacy/print_result.html +0 -30
- zebra_day/templates/legacy/printer_details.html +0 -25
- zebra_day/templates/legacy/printer_status.html +0 -70
- zebra_day/templates/legacy/save_result.html +0 -17
- zebra_day/templates/legacy/send_print_request.html +0 -34
- zebra_day/templates/legacy/simple_print.html +0 -94
- zebra_day/templates/legacy/view_pstation_json.html +0 -29
- zebra_day/templates/list_prior_configs.html +0 -24
- zebra_day/templates/print_result.html +0 -30
- zebra_day/templates/printer_details.html +0 -25
- zebra_day/templates/printer_status.html +0 -70
- zebra_day/templates/save_result.html +0 -17
- zebra_day/templates/send_print_request.html +0 -34
- zebra_day/templates/simple_print.html +0 -94
- zebra_day/templates/view_pstation_json.html +0 -29
- zebra_day-1.0.2.dist-info/RECORD +0 -179
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/WHEEL +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/entry_points.txt +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/licenses/LICENSE +0 -0
- {zebra_day-1.0.2.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,38 +40,122 @@ 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
|
-
"""Printer information model."""
|
|
43
|
-
|
|
50
|
+
"""Printer information model (v2.0.0 schema)."""
|
|
51
|
+
|
|
52
|
+
id: str = Field(..., description="Printer identifier/key in JSON")
|
|
44
53
|
ip_address: str
|
|
54
|
+
printer_name: str | None = Field(None, description="User-friendly display name")
|
|
55
|
+
lab_location: str | None = Field(None, description="Location within the lab")
|
|
56
|
+
manufacturer: str = Field("zebra", description="Printer manufacturer")
|
|
45
57
|
model: str
|
|
46
58
|
serial: str
|
|
47
|
-
label_zpl_styles:
|
|
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
|
+
)
|
|
48
63
|
print_method: str
|
|
64
|
+
notes: str | None = Field("", description="Optional notes")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class LabInfo(BaseModel):
|
|
68
|
+
"""Lab information model (v2.0.0 schema)."""
|
|
69
|
+
|
|
70
|
+
id: str = Field(..., description="Lab identifier/key in JSON")
|
|
71
|
+
lab_name: str = Field(..., description="Human-readable lab name")
|
|
72
|
+
available_locations: list[str] = Field(
|
|
73
|
+
default_factory=list, description="Valid location options for printers"
|
|
74
|
+
)
|
|
75
|
+
printers: list[PrinterInfo]
|
|
49
76
|
|
|
50
77
|
|
|
51
78
|
class LabPrinters(BaseModel):
|
|
52
|
-
"""Lab and its printers."""
|
|
79
|
+
"""Lab and its printers (deprecated, use LabInfo)."""
|
|
80
|
+
|
|
53
81
|
lab: str
|
|
54
|
-
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")
|
|
55
107
|
|
|
56
108
|
|
|
57
109
|
# ----- Endpoints -----
|
|
58
110
|
|
|
59
|
-
|
|
60
|
-
|
|
111
|
+
|
|
112
|
+
@router.get("/labs", response_model=list[str])
|
|
113
|
+
async def list_labs(request: Request) -> list[str]:
|
|
61
114
|
"""List all available labs."""
|
|
62
115
|
zp = request.app.state.zp
|
|
63
116
|
return list(zp.printers.get("labs", {}).keys())
|
|
64
117
|
|
|
65
118
|
|
|
66
|
-
@router.get("/labs/{lab}
|
|
67
|
-
async def
|
|
119
|
+
@router.get("/labs/{lab}", response_model=LabInfo)
|
|
120
|
+
async def get_lab(request: Request, lab: str) -> LabInfo:
|
|
121
|
+
"""Get lab details including available locations and printers."""
|
|
122
|
+
zp = request.app.state.zp
|
|
123
|
+
labs = zp.printers.get("labs", {})
|
|
124
|
+
|
|
125
|
+
if lab not in labs:
|
|
126
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
127
|
+
|
|
128
|
+
lab_data = labs[lab]
|
|
129
|
+
lab_printers = lab_data.get("printers", {})
|
|
130
|
+
|
|
131
|
+
printers = []
|
|
132
|
+
for printer_id, info in lab_printers.items():
|
|
133
|
+
printers.append(
|
|
134
|
+
PrinterInfo(
|
|
135
|
+
id=printer_id,
|
|
136
|
+
ip_address=info.get("ip_address", ""),
|
|
137
|
+
printer_name=info.get("printer_name"),
|
|
138
|
+
lab_location=info.get("lab_location"),
|
|
139
|
+
manufacturer=info.get("manufacturer", "zebra"),
|
|
140
|
+
model=info.get("model", ""),
|
|
141
|
+
serial=info.get("serial", ""),
|
|
142
|
+
label_zpl_styles=info.get("label_zpl_styles", []),
|
|
143
|
+
default_label_style=info.get("default_label_style"),
|
|
144
|
+
print_method=info.get("print_method", ""),
|
|
145
|
+
notes=info.get("notes", ""),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return LabInfo(
|
|
150
|
+
id=lab,
|
|
151
|
+
lab_name=lab_data.get("lab_name", lab),
|
|
152
|
+
available_locations=lab_data.get("available_locations", []),
|
|
153
|
+
printers=printers,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@router.get("/labs/{lab}/printers", response_model=list[PrinterInfo])
|
|
158
|
+
async def list_printers(request: Request, lab: str) -> list[PrinterInfo]:
|
|
68
159
|
"""List all printers in a lab."""
|
|
69
160
|
zp = request.app.state.zp
|
|
70
161
|
labs = zp.printers.get("labs", {})
|
|
@@ -72,25 +163,32 @@ async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
|
|
|
72
163
|
if lab not in labs:
|
|
73
164
|
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
74
165
|
|
|
166
|
+
# Access printers via nested 'printers' key (v2 schema)
|
|
167
|
+
lab_printers = labs[lab].get("printers", {})
|
|
168
|
+
|
|
75
169
|
printers = []
|
|
76
|
-
for
|
|
170
|
+
for printer_id, info in lab_printers.items():
|
|
77
171
|
printers.append(
|
|
78
172
|
PrinterInfo(
|
|
79
|
-
|
|
173
|
+
id=printer_id,
|
|
80
174
|
ip_address=info.get("ip_address", ""),
|
|
175
|
+
printer_name=info.get("printer_name"),
|
|
176
|
+
lab_location=info.get("lab_location"),
|
|
177
|
+
manufacturer=info.get("manufacturer", "zebra"),
|
|
81
178
|
model=info.get("model", ""),
|
|
82
179
|
serial=info.get("serial", ""),
|
|
83
180
|
label_zpl_styles=info.get("label_zpl_styles", []),
|
|
181
|
+
default_label_style=info.get("default_label_style"),
|
|
84
182
|
print_method=info.get("print_method", ""),
|
|
183
|
+
notes=info.get("notes", ""),
|
|
85
184
|
)
|
|
86
185
|
)
|
|
87
186
|
return printers
|
|
88
187
|
|
|
89
188
|
|
|
90
|
-
@router.get("/templates", response_model=
|
|
91
|
-
async def list_templates(request: Request) ->
|
|
189
|
+
@router.get("/templates", response_model=list[str])
|
|
190
|
+
async def list_templates(request: Request) -> list[str]:
|
|
92
191
|
"""List all available ZPL templates."""
|
|
93
|
-
from pathlib import Path
|
|
94
192
|
|
|
95
193
|
pkg_path = request.app.state.pkg_path
|
|
96
194
|
styles_dir = pkg_path / "etc" / "label_styles"
|
|
@@ -124,7 +222,7 @@ async def print_label(request: Request, print_req: PrintRequest) -> PrintRespons
|
|
|
124
222
|
raise HTTPException(status_code=429, detail=reason)
|
|
125
223
|
|
|
126
224
|
try:
|
|
127
|
-
|
|
225
|
+
zp.print_zpl(
|
|
128
226
|
lab=print_req.lab,
|
|
129
227
|
printer_name=print_req.printer,
|
|
130
228
|
label_zpl_style=print_req.label_zpl_style,
|
|
@@ -138,26 +236,227 @@ async def print_label(request: Request, print_req: PrintRequest) -> PrintRespons
|
|
|
138
236
|
print_n=print_req.copies,
|
|
139
237
|
client_ip=client_ip,
|
|
140
238
|
)
|
|
141
|
-
|
|
142
|
-
# Check if result is a PNG file path
|
|
143
|
-
if result and ".png" in str(result):
|
|
144
|
-
png_name = str(result).split("/")[-1]
|
|
145
|
-
return PrintResponse(
|
|
146
|
-
success=True,
|
|
147
|
-
message="PNG generated successfully",
|
|
148
|
-
png_url=f"/files/{png_name}",
|
|
149
|
-
)
|
|
150
|
-
|
|
151
239
|
return PrintResponse(success=True, message="Print request sent successfully")
|
|
152
240
|
except Exception as e:
|
|
153
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
241
|
+
raise HTTPException(status_code=500, detail=str(e)) from None
|
|
154
242
|
finally:
|
|
155
243
|
rate_limiter.release()
|
|
156
244
|
|
|
157
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
|
+
|
|
158
355
|
@router.get("/config")
|
|
159
|
-
async def get_config(request: Request) ->
|
|
356
|
+
async def get_config(request: Request) -> dict[str, Any]:
|
|
160
357
|
"""Get the current printer configuration."""
|
|
161
358
|
zp = request.app.state.zp
|
|
162
|
-
return zp.printers
|
|
359
|
+
return dict(zp.printers)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ----- Lab Settings Endpoints -----
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class LabUpdateRequest(BaseModel):
|
|
366
|
+
"""Request model for updating lab settings."""
|
|
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")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class PrinterUpdateRequest(BaseModel):
|
|
373
|
+
"""Request model for updating printer settings."""
|
|
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
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@router.patch("/labs/{lab}", response_model=LabInfo)
|
|
385
|
+
async def update_lab(request: Request, lab: str, update: LabUpdateRequest) -> LabInfo:
|
|
386
|
+
"""Update lab settings (lab_name, available_locations)."""
|
|
387
|
+
zp = request.app.state.zp
|
|
388
|
+
labs = zp.printers.get("labs", {})
|
|
389
|
+
|
|
390
|
+
if lab not in labs:
|
|
391
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
392
|
+
|
|
393
|
+
lab_data = labs[lab]
|
|
394
|
+
|
|
395
|
+
if update.lab_name is not None:
|
|
396
|
+
lab_data["lab_name"] = update.lab_name
|
|
397
|
+
if update.available_locations is not None:
|
|
398
|
+
lab_data["available_locations"] = update.available_locations
|
|
399
|
+
|
|
400
|
+
# Save changes
|
|
401
|
+
zp.save_printer_json(zp.printers_filename, relative=False)
|
|
402
|
+
|
|
403
|
+
# Return updated lab info
|
|
404
|
+
return await get_lab(request, lab)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@router.patch("/labs/{lab}/printers/{printer_id}")
|
|
408
|
+
async def update_printer(
|
|
409
|
+
request: Request, lab: str, printer_id: str, update: PrinterUpdateRequest
|
|
410
|
+
) -> PrinterInfo:
|
|
411
|
+
"""Update printer settings (printer_name, lab_location, notes)."""
|
|
412
|
+
zp = request.app.state.zp
|
|
413
|
+
labs = zp.printers.get("labs", {})
|
|
414
|
+
|
|
415
|
+
if lab not in labs:
|
|
416
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
417
|
+
|
|
418
|
+
lab_printers = labs[lab].get("printers", {})
|
|
419
|
+
if printer_id not in lab_printers:
|
|
420
|
+
raise HTTPException(
|
|
421
|
+
status_code=404, detail=f"Printer '{printer_id}' not found in lab '{lab}'"
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
printer_data = lab_printers[printer_id]
|
|
425
|
+
|
|
426
|
+
if update.printer_name is not None:
|
|
427
|
+
printer_data["printer_name"] = update.printer_name if update.printer_name else None
|
|
428
|
+
if update.lab_location is not None:
|
|
429
|
+
printer_data["lab_location"] = update.lab_location if update.lab_location else None
|
|
430
|
+
if update.notes is not None:
|
|
431
|
+
printer_data["notes"] = update.notes
|
|
432
|
+
if update.label_zpl_styles is not None:
|
|
433
|
+
printer_data["label_zpl_styles"] = update.label_zpl_styles
|
|
434
|
+
if update.default_label_style is not None:
|
|
435
|
+
# Validate that the style exists in label_zpl_styles (if it's not empty string)
|
|
436
|
+
if update.default_label_style and update.default_label_style not in printer_data.get(
|
|
437
|
+
"label_zpl_styles", []
|
|
438
|
+
):
|
|
439
|
+
raise HTTPException(
|
|
440
|
+
status_code=400,
|
|
441
|
+
detail=f"Default label style '{update.default_label_style}' must be one of: {printer_data.get('label_zpl_styles', [])}",
|
|
442
|
+
)
|
|
443
|
+
printer_data["default_label_style"] = (
|
|
444
|
+
update.default_label_style if update.default_label_style else None
|
|
445
|
+
)
|
|
163
446
|
|
|
447
|
+
# Save changes
|
|
448
|
+
zp.save_printer_json(zp.printers_filename, relative=False)
|
|
449
|
+
|
|
450
|
+
return PrinterInfo(
|
|
451
|
+
id=printer_id,
|
|
452
|
+
ip_address=printer_data.get("ip_address", ""),
|
|
453
|
+
printer_name=printer_data.get("printer_name"),
|
|
454
|
+
lab_location=printer_data.get("lab_location"),
|
|
455
|
+
manufacturer=printer_data.get("manufacturer", "zebra"),
|
|
456
|
+
model=printer_data.get("model", ""),
|
|
457
|
+
serial=printer_data.get("serial", ""),
|
|
458
|
+
label_zpl_styles=printer_data.get("label_zpl_styles", []),
|
|
459
|
+
default_label_style=printer_data.get("default_label_style"),
|
|
460
|
+
print_method=printer_data.get("print_method", ""),
|
|
461
|
+
notes=printer_data.get("notes", ""),
|
|
462
|
+
)
|