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.
- zebra_day/__init__.py +35 -0
- zebra_day/bin/fetch_zebra_config.py +15 -0
- zebra_day/bin/generate_coord_grid_zpl.py +50 -0
- zebra_day/bin/print_zpl_from_file.py +21 -0
- zebra_day/bin/probe_new_label_dimensions.py +75 -0
- zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +2 -1
- zebra_day/bin/zserve.py +701 -259
- zebra_day/cli/__init__.py +240 -0
- zebra_day/cli/cognito.py +121 -0
- zebra_day/cli/gui.py +255 -0
- zebra_day/cli/printer.py +168 -0
- zebra_day/cli/template.py +176 -0
- zebra_day/cmd_mgr.py +35 -0
- zebra_day/etc/label_styles/blank.zpl +0 -0
- zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
- zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
- zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
- zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
- zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
- zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
- zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
- zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
- zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
- zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
- zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
- zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
- zebra_day/etc/label_styles/tube_2inX0.5in.zpl +15 -0
- zebra_day/etc/label_styles/tube_2inX0.5inHD.zpl +15 -0
- zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
- zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
- 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
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
- zebra_day/etc/printer_config.json +3 -54
- zebra_day/etc/printer_config.template.json +3 -2
- zebra_day/etc/tmp_printers0.json +5 -0
- zebra_day/etc/tmp_printers374.json +5 -0
- zebra_day/etc/tmp_printers383.json +5 -0
- zebra_day/etc/tmp_printers450.json +5 -0
- zebra_day/etc/tmp_printers504.json +5 -0
- zebra_day/etc/tmp_printers608.json +5 -0
- zebra_day/etc/tmp_printers657.json +5 -0
- zebra_day/etc/tmp_printers838.json +5 -0
- zebra_day/etc/tmp_printers839.json +5 -0
- zebra_day/etc/tmp_printers933.json +5 -0
- zebra_day/etc/tmp_printers957.json +5 -0
- zebra_day/exceptions.py +88 -0
- zebra_day/files/hold +0 -0
- zebra_day/files/test_png_17696.png +0 -0
- zebra_day/files/test_png_23477.png +0 -0
- zebra_day/files/test_png_28157.png +0 -0
- zebra_day/files/test_png_35832.png +0 -0
- zebra_day/files/test_png_36400.png +0 -0
- zebra_day/files/test_png_40816.png +0 -0
- zebra_day/files/test_png_49564.png +0 -0
- zebra_day/files/test_png_53848.png +0 -0
- zebra_day/files/test_png_62542.png +0 -0
- zebra_day/files/test_png_91597.png +0 -0
- zebra_day/files/test_png_93633.png +0 -0
- zebra_day/files/tmpbjo3k7q1.png +0 -0
- zebra_day/files/tmpigtr4pwy.png +0 -0
- zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
- zebra_day/logging_config.py +74 -0
- zebra_day/logs/.hold +0 -0
- zebra_day/logs/print_requests.log +2 -0
- zebra_day/paths.py +143 -0
- zebra_day/print_mgr.py +489 -103
- zebra_day/static/datschund.css +63 -43
- zebra_day/static/datschund.png +0 -0
- zebra_day/static/daylily.png +0 -0
- zebra_day/static/favicon.svg +20 -0
- zebra_day/static/general.css +99 -0
- zebra_day/static/js/zebra_modern.js +172 -0
- zebra_day/static/lsmc.css +354 -0
- zebra_day/static/moon.jpeg +0 -0
- zebra_day/static/oakland.css +0 -32
- zebra_day/static/popday_daylily.css +1 -1
- zebra_day/static/style.css +39 -0
- zebra_day/static/zebra_modern.css +771 -0
- zebra_day/templates/base.html +36 -0
- zebra_day/templates/bpr.html +72 -0
- zebra_day/templates/build_new_config.html +36 -0
- zebra_day/templates/build_print_request.html +32 -0
- zebra_day/templates/chg_ui_style.html +19 -0
- zebra_day/templates/edit_template.html +128 -0
- zebra_day/templates/edit_zpl.html +37 -0
- zebra_day/templates/index.html +82 -0
- zebra_day/templates/legacy/base.html +37 -0
- zebra_day/templates/legacy/bpr.html +72 -0
- zebra_day/templates/legacy/build_new_config.html +36 -0
- zebra_day/templates/legacy/build_print_request.html +32 -0
- zebra_day/templates/legacy/chg_ui_style.html +19 -0
- zebra_day/templates/legacy/edit_template.html +128 -0
- zebra_day/templates/legacy/edit_zpl.html +37 -0
- zebra_day/templates/legacy/index.html +82 -0
- zebra_day/templates/legacy/list_prior_configs.html +24 -0
- zebra_day/templates/legacy/print_result.html +30 -0
- zebra_day/templates/legacy/printer_details.html +25 -0
- zebra_day/templates/legacy/printer_status.html +70 -0
- zebra_day/templates/legacy/save_result.html +17 -0
- zebra_day/templates/legacy/send_print_request.html +34 -0
- zebra_day/templates/legacy/simple_print.html +94 -0
- zebra_day/templates/legacy/view_pstation_json.html +29 -0
- zebra_day/templates/list_prior_configs.html +24 -0
- zebra_day/templates/modern/base.html +98 -0
- zebra_day/templates/modern/config.html +141 -0
- zebra_day/templates/modern/dashboard.html +160 -0
- zebra_day/templates/modern/print_request.html +141 -0
- zebra_day/templates/modern/print_result.html +88 -0
- zebra_day/templates/modern/printer_detail.html +117 -0
- zebra_day/templates/modern/printers.html +133 -0
- zebra_day/templates/modern/save_result.html +46 -0
- zebra_day/templates/modern/template_editor.html +172 -0
- zebra_day/templates/modern/templates.html +122 -0
- zebra_day/templates/print_result.html +30 -0
- zebra_day/templates/printer_details.html +25 -0
- zebra_day/templates/printer_status.html +70 -0
- zebra_day/templates/save_result.html +17 -0
- zebra_day/templates/send_print_request.html +34 -0
- zebra_day/templates/simple_print.html +94 -0
- zebra_day/templates/view_pstation_json.html +29 -0
- zebra_day/web/__init__.py +9 -0
- zebra_day/web/app.py +171 -0
- zebra_day/web/auth.py +172 -0
- zebra_day/web/middleware.py +159 -0
- zebra_day/web/routers/__init__.py +2 -0
- zebra_day/web/routers/api.py +163 -0
- zebra_day/web/routers/ui.py +1051 -0
- zebra_day/zpl_renderer.py +273 -0
- zebra_day-1.0.2.dist-info/METADATA +786 -0
- zebra_day-1.0.2.dist-info/RECORD +179 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/WHEEL +1 -1
- zebra_day-1.0.2.dist-info/entry_points.txt +4 -0
- zebra_day/etc/.blind +0 -1
- zebra_day/etc/current_style.txt +0 -1
- zebra_day/etc/label_styles/test_2inX1in.zpl +0 -22
- zebra_day/etc/label_styles/tmps/labware_2inX1in.na.2023-10-24_12:49:21.045127.zpl +0 -21
- 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
- zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.zpl +0 -21
- zebra_day/etc/label_styles/tmps/test_2inX1in.na.2023-10-24_12:45:37.002774.zpl +0 -22
- zebra_day/etc/old_printer_config/2023-10-24_16:06:06.931764_printer_config.json +0 -67
- zebra_day/etc/printer_config.json~ +0 -67
- zebra_day/files/tmp_2olihg4.png +0 -0
- zebra_day/files/tmpveojoyvn.png +0 -0
- zebra_day/files/zpl_label_labware_2inX1in_2023-10-25_02:30:08.093631.png +0 -0
- zebra_day/files/zpl_label_test_2inX1in_2023-10-24_15:54:29.343124.png +0 -0
- zebra_day/files/zpl_label_test_2inX1in_2023-10-24_16:01:45.670132.png +0 -0
- zebra_day/static/beyonce.css +0 -227
- zebra_day/static/datschund_on_moon.css +0 -164
- zebra_day/static/medicalsci.css +0 -144
- zebra_day/static/moar_zebra.css +0 -133
- zebra_day/static/popday.css +0 -140
- zebra_day/static/popday_dark.css +0 -140
- zebra_day/static/popday_dog.css +0 -140
- zebra_day-0.0.32.dist-info/METADATA +0 -14
- zebra_day-0.0.32.dist-info/RECORD +0 -53
- zebra_day-0.0.32.dist-info/entry_points.txt +0 -2
- /zebra_day/{etc/label_styles/blank_0inX0in.zpl → bin/__init__.py} +0 -0
- /zebra_day/etc/label_styles/{labware_2inX1in.zpl → generic_2inX1in.zpl} +0 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info/licenses}/LICENSE +0 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Middleware for the zebra_day FastAPI application.
|
|
3
|
+
|
|
4
|
+
Provides request logging and rate limiting functionality.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import time
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from typing import Callable
|
|
12
|
+
|
|
13
|
+
from fastapi import Request, Response
|
|
14
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
15
|
+
|
|
16
|
+
from zebra_day.logging_config import get_logger
|
|
17
|
+
|
|
18
|
+
_log = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
22
|
+
"""
|
|
23
|
+
Middleware for structured request logging.
|
|
24
|
+
|
|
25
|
+
Logs client IP, request path, method, timing, and response status.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
29
|
+
"""Process request and log structured data."""
|
|
30
|
+
start_time = time.perf_counter()
|
|
31
|
+
|
|
32
|
+
# Extract client info
|
|
33
|
+
client_ip = request.client.host if request.client else "unknown"
|
|
34
|
+
method = request.method
|
|
35
|
+
path = request.url.path
|
|
36
|
+
query = str(request.query_params) if request.query_params else ""
|
|
37
|
+
|
|
38
|
+
# Extract relevant parameters for print operations
|
|
39
|
+
lab = request.query_params.get("lab", "")
|
|
40
|
+
printer = request.query_params.get("printer", "")
|
|
41
|
+
template = request.query_params.get("label_zpl_style", "")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
response = await call_next(request)
|
|
45
|
+
status_code = response.status_code
|
|
46
|
+
outcome = "success" if status_code < 400 else "error"
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
status_code = 500
|
|
49
|
+
outcome = "exception"
|
|
50
|
+
_log.exception(
|
|
51
|
+
"Request failed",
|
|
52
|
+
extra={
|
|
53
|
+
"client_ip": client_ip,
|
|
54
|
+
"method": method,
|
|
55
|
+
"path": path,
|
|
56
|
+
"error": str(exc),
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
62
|
+
|
|
63
|
+
# Build log context
|
|
64
|
+
log_context = {
|
|
65
|
+
"client_ip": client_ip,
|
|
66
|
+
"method": method,
|
|
67
|
+
"path": path,
|
|
68
|
+
"status_code": status_code,
|
|
69
|
+
"elapsed_ms": round(elapsed_ms, 2),
|
|
70
|
+
"outcome": outcome,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Add print-specific context if relevant
|
|
74
|
+
if lab:
|
|
75
|
+
log_context["lab"] = lab
|
|
76
|
+
if printer:
|
|
77
|
+
log_context["printer"] = printer
|
|
78
|
+
if template:
|
|
79
|
+
log_context["template"] = template
|
|
80
|
+
|
|
81
|
+
# Log at appropriate level
|
|
82
|
+
if status_code >= 500:
|
|
83
|
+
_log.error("Request completed", extra=log_context)
|
|
84
|
+
elif status_code >= 400:
|
|
85
|
+
_log.warning("Request completed", extra=log_context)
|
|
86
|
+
else:
|
|
87
|
+
_log.info("Request completed", extra=log_context)
|
|
88
|
+
|
|
89
|
+
return response
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class PrintRateLimiter:
|
|
93
|
+
"""
|
|
94
|
+
Simple rate limiter for print endpoints.
|
|
95
|
+
|
|
96
|
+
Uses a sliding window approach with configurable limits.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
max_requests: int = 10,
|
|
102
|
+
window_seconds: float = 60.0,
|
|
103
|
+
max_concurrent: int = 3,
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
Initialize rate limiter.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
max_requests: Maximum requests per window per client IP
|
|
110
|
+
window_seconds: Time window in seconds
|
|
111
|
+
max_concurrent: Maximum concurrent print operations
|
|
112
|
+
"""
|
|
113
|
+
self.max_requests = max_requests
|
|
114
|
+
self.window_seconds = window_seconds
|
|
115
|
+
self.max_concurrent = max_concurrent
|
|
116
|
+
|
|
117
|
+
self._request_times: dict[str, list[float]] = defaultdict(list)
|
|
118
|
+
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
119
|
+
self._lock = asyncio.Lock()
|
|
120
|
+
|
|
121
|
+
async def acquire(self, client_ip: str) -> tuple[bool, str]:
|
|
122
|
+
"""
|
|
123
|
+
Try to acquire a print slot.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Tuple of (allowed, reason)
|
|
127
|
+
"""
|
|
128
|
+
now = time.time()
|
|
129
|
+
|
|
130
|
+
async with self._lock:
|
|
131
|
+
# Clean old entries
|
|
132
|
+
cutoff = now - self.window_seconds
|
|
133
|
+
self._request_times[client_ip] = [
|
|
134
|
+
t for t in self._request_times[client_ip] if t > cutoff
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
# Check rate limit
|
|
138
|
+
if len(self._request_times[client_ip]) >= self.max_requests:
|
|
139
|
+
return False, f"Rate limit exceeded: {self.max_requests} requests per {self.window_seconds}s"
|
|
140
|
+
|
|
141
|
+
# Try to acquire semaphore (non-blocking check)
|
|
142
|
+
if self._semaphore.locked() and self._semaphore._value == 0:
|
|
143
|
+
return False, f"Too many concurrent print operations (max {self.max_concurrent})"
|
|
144
|
+
|
|
145
|
+
# Record this request
|
|
146
|
+
self._request_times[client_ip].append(now)
|
|
147
|
+
|
|
148
|
+
# Acquire semaphore for actual operation
|
|
149
|
+
await self._semaphore.acquire()
|
|
150
|
+
return True, ""
|
|
151
|
+
|
|
152
|
+
def release(self) -> None:
|
|
153
|
+
"""Release a print slot after operation completes."""
|
|
154
|
+
self._semaphore.release()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Global rate limiter instance
|
|
158
|
+
print_rate_limiter = PrintRateLimiter()
|
|
159
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Versioned JSON API router for zebra_day.
|
|
3
|
+
|
|
4
|
+
Provides programmatic access to printer management and label printing.
|
|
5
|
+
All endpoints return JSON and are prefixed with /api/v1/.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional, List, Dict, Any
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ----- Request/Response Models -----
|
|
18
|
+
|
|
19
|
+
class PrintRequest(BaseModel):
|
|
20
|
+
"""Request model for printing a label."""
|
|
21
|
+
lab: str = Field(..., description="Lab identifier")
|
|
22
|
+
printer: str = Field(..., description="Printer name")
|
|
23
|
+
label_zpl_style: Optional[str] = Field(None, description="ZPL template name")
|
|
24
|
+
uid_barcode: str = Field("", description="UID for barcode")
|
|
25
|
+
alt_a: str = Field("", description="Alternative field A")
|
|
26
|
+
alt_b: str = Field("", description="Alternative field B")
|
|
27
|
+
alt_c: str = Field("", description="Alternative field C")
|
|
28
|
+
alt_d: str = Field("", description="Alternative field D")
|
|
29
|
+
alt_e: str = Field("", description="Alternative field E")
|
|
30
|
+
alt_f: str = Field("", description="Alternative field F")
|
|
31
|
+
copies: int = Field(1, ge=1, le=100, description="Number of copies")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PrintResponse(BaseModel):
|
|
35
|
+
"""Response model for print request."""
|
|
36
|
+
success: bool
|
|
37
|
+
message: str
|
|
38
|
+
png_url: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PrinterInfo(BaseModel):
|
|
42
|
+
"""Printer information model."""
|
|
43
|
+
name: str
|
|
44
|
+
ip_address: str
|
|
45
|
+
model: str
|
|
46
|
+
serial: str
|
|
47
|
+
label_zpl_styles: List[str]
|
|
48
|
+
print_method: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LabPrinters(BaseModel):
|
|
52
|
+
"""Lab and its printers."""
|
|
53
|
+
lab: str
|
|
54
|
+
printers: List[PrinterInfo]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ----- Endpoints -----
|
|
58
|
+
|
|
59
|
+
@router.get("/labs", response_model=List[str])
|
|
60
|
+
async def list_labs(request: Request) -> List[str]:
|
|
61
|
+
"""List all available labs."""
|
|
62
|
+
zp = request.app.state.zp
|
|
63
|
+
return list(zp.printers.get("labs", {}).keys())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.get("/labs/{lab}/printers", response_model=List[PrinterInfo])
|
|
67
|
+
async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
|
|
68
|
+
"""List all printers in a lab."""
|
|
69
|
+
zp = request.app.state.zp
|
|
70
|
+
labs = zp.printers.get("labs", {})
|
|
71
|
+
|
|
72
|
+
if lab not in labs:
|
|
73
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
74
|
+
|
|
75
|
+
printers = []
|
|
76
|
+
for name, info in labs[lab].items():
|
|
77
|
+
printers.append(
|
|
78
|
+
PrinterInfo(
|
|
79
|
+
name=name,
|
|
80
|
+
ip_address=info.get("ip_address", ""),
|
|
81
|
+
model=info.get("model", ""),
|
|
82
|
+
serial=info.get("serial", ""),
|
|
83
|
+
label_zpl_styles=info.get("label_zpl_styles", []),
|
|
84
|
+
print_method=info.get("print_method", ""),
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return printers
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.get("/templates", response_model=List[str])
|
|
91
|
+
async def list_templates(request: Request) -> List[str]:
|
|
92
|
+
"""List all available ZPL templates."""
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
|
|
95
|
+
pkg_path = request.app.state.pkg_path
|
|
96
|
+
styles_dir = pkg_path / "etc" / "label_styles"
|
|
97
|
+
|
|
98
|
+
templates = []
|
|
99
|
+
if styles_dir.exists():
|
|
100
|
+
for f in styles_dir.iterdir():
|
|
101
|
+
if f.is_file() and f.suffix == ".zpl":
|
|
102
|
+
templates.append(f.stem)
|
|
103
|
+
|
|
104
|
+
# Also include drafts
|
|
105
|
+
tmps_dir = styles_dir / "tmps"
|
|
106
|
+
if tmps_dir.exists():
|
|
107
|
+
for f in tmps_dir.iterdir():
|
|
108
|
+
if f.is_file() and f.suffix == ".zpl":
|
|
109
|
+
templates.append(f.stem)
|
|
110
|
+
|
|
111
|
+
return sorted(templates)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router.post("/print", response_model=PrintResponse)
|
|
115
|
+
async def print_label(request: Request, print_req: PrintRequest) -> PrintResponse:
|
|
116
|
+
"""Send a print request to a printer."""
|
|
117
|
+
zp = request.app.state.zp
|
|
118
|
+
rate_limiter = request.app.state.print_rate_limiter
|
|
119
|
+
client_ip = request.client.host if request.client else "unknown"
|
|
120
|
+
|
|
121
|
+
# Check rate limit
|
|
122
|
+
allowed, reason = await rate_limiter.acquire(client_ip)
|
|
123
|
+
if not allowed:
|
|
124
|
+
raise HTTPException(status_code=429, detail=reason)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
result = zp.print_zpl(
|
|
128
|
+
lab=print_req.lab,
|
|
129
|
+
printer_name=print_req.printer,
|
|
130
|
+
label_zpl_style=print_req.label_zpl_style,
|
|
131
|
+
uid_barcode=print_req.uid_barcode,
|
|
132
|
+
alt_a=print_req.alt_a,
|
|
133
|
+
alt_b=print_req.alt_b,
|
|
134
|
+
alt_c=print_req.alt_c,
|
|
135
|
+
alt_d=print_req.alt_d,
|
|
136
|
+
alt_e=print_req.alt_e,
|
|
137
|
+
alt_f=print_req.alt_f,
|
|
138
|
+
print_n=print_req.copies,
|
|
139
|
+
client_ip=client_ip,
|
|
140
|
+
)
|
|
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
|
+
return PrintResponse(success=True, message="Print request sent successfully")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
154
|
+
finally:
|
|
155
|
+
rate_limiter.release()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@router.get("/config")
|
|
159
|
+
async def get_config(request: Request) -> Dict[str, Any]:
|
|
160
|
+
"""Get the current printer configuration."""
|
|
161
|
+
zp = request.app.state.zp
|
|
162
|
+
return zp.printers
|
|
163
|
+
|