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