zebra-day 1.0.2__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/cli/gui.py +89 -6
- zebra_day/etc/printer_config.json +2 -14
- zebra_day/etc/printer_config.template.json +17 -9
- 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_printers469.json +10 -0
- zebra_day/etc/tmp_printers485.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_printers552.json +10 -0
- zebra_day/etc/tmp_printers715.json +10 -0
- zebra_day/etc/tmp_printers972.json +10 -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/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_30069.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_58809.png +0 -0
- zebra_day/files/test_png_67242.png +0 -0
- zebra_day/files/test_png_89893.png +0 -0
- zebra_day/files/tube_20mmX30mmA_preview.png +0 -0
- zebra_day/print_mgr.py +136 -80
- 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 +9 -5
- 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/app.py +84 -7
- zebra_day/web/routers/api.py +155 -5
- zebra_day/web/routers/ui.py +155 -570
- {zebra_day-1.0.2.dist-info → zebra_day-2.0.0.dist-info}/METADATA +74 -13
- {zebra_day-1.0.2.dist-info → zebra_day-2.0.0.dist-info}/RECORD +46 -57
- 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 → zebra_day-2.0.0.dist-info}/WHEEL +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.0.0.dist-info}/entry_points.txt +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.0.0.dist-info}/top_level.txt +0 -0
zebra_day/print_mgr.py
CHANGED
|
@@ -25,6 +25,7 @@ from importlib.resources import files
|
|
|
25
25
|
|
|
26
26
|
from zebra_day.logging_config import get_logger
|
|
27
27
|
from zebra_day import paths as xdg
|
|
28
|
+
import zebra_day.cmd_mgr as zdcm
|
|
28
29
|
|
|
29
30
|
_log = get_logger(__name__)
|
|
30
31
|
|
|
@@ -123,9 +124,10 @@ class zpl:
|
|
|
123
124
|
self.create_new_printers_json_with_single_test_printer(str(jcfg))
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
def probe_zebra_printers_add_to_printers_json(self, ip_stub="192.168.1", scan_wait="0.25",lab="scan-results", relative=False):
|
|
127
|
+
def probe_zebra_printers_add_to_printers_json(self, ip_stub="192.168.1", scan_wait="0.25", lab="scan-results", relative=False):
|
|
127
128
|
"""
|
|
128
|
-
Scan the network for zebra printers
|
|
129
|
+
Scan the network for zebra printers.
|
|
130
|
+
|
|
129
131
|
NOTE! this should work with no dependencies on a MAC
|
|
130
132
|
UBUNTU requires system wide net-tools (for arp)
|
|
131
133
|
Others... well, this may not work
|
|
@@ -133,61 +135,106 @@ class zpl:
|
|
|
133
135
|
---
|
|
134
136
|
Requires:
|
|
135
137
|
curl is pretty standard, arp seems less so
|
|
136
|
-
arp
|
|
138
|
+
arp
|
|
137
139
|
---
|
|
138
|
-
|
|
139
|
-
ip_stub = all 255 possibilities will be probed beneath this
|
|
140
|
-
stub provided
|
|
141
|
-
|
|
142
|
-
can_wait = seconds to re-try probing until moving on. 0.25
|
|
143
|
-
default may be too squick
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
ip_stub = all 255 possibilities will be probed beneath this stub provided
|
|
142
|
+
scan_wait = seconds to re-try probing until moving on. 0.25 default may be too quick
|
|
143
|
+
lab = code for the lab key to add/update to given finding new printers
|
|
147
144
|
"""
|
|
145
|
+
# Ensure schema version is set
|
|
146
|
+
if "schema_version" not in self.printers:
|
|
147
|
+
self.printers["schema_version"] = "2.0.0"
|
|
148
148
|
|
|
149
|
+
# Initialize lab with v2 structure if not exists
|
|
149
150
|
if lab not in self.printers['labs']:
|
|
150
|
-
self.printers['labs'][lab] = {
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
self.printers['labs'][lab] = {
|
|
152
|
+
"lab_name": lab,
|
|
153
|
+
"available_locations": [],
|
|
154
|
+
"printers": {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Ensure lab has printers sub-object (migration from v1)
|
|
158
|
+
if "printers" not in self.printers['labs'][lab]:
|
|
159
|
+
self.printers['labs'][lab]["printers"] = {}
|
|
160
|
+
self.printers['labs'][lab].setdefault("lab_name", lab)
|
|
161
|
+
self.printers['labs'][lab].setdefault("available_locations", [])
|
|
162
|
+
|
|
163
|
+
# Add the virtual PNG printer
|
|
164
|
+
self.printers['labs'][lab]["printers"]["Download-Label-png"] = {
|
|
153
165
|
"ip_address": "dl_png",
|
|
154
|
-
"
|
|
155
|
-
"
|
|
166
|
+
"printer_name": "Download Label as PNG",
|
|
167
|
+
"lab_location": None,
|
|
168
|
+
"manufacturer": "virtual",
|
|
156
169
|
"model": "na",
|
|
157
170
|
"serial": "na",
|
|
158
|
-
"
|
|
171
|
+
"label_zpl_styles": ["tube_2inX1in"],
|
|
172
|
+
"default_label_style": "tube_2inX1in",
|
|
173
|
+
"print_method": "generate png",
|
|
174
|
+
"arp_data": "",
|
|
175
|
+
"notes": ""
|
|
159
176
|
}
|
|
160
177
|
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
# Scan network for Zebra printers using pure Python
|
|
179
|
+
wait_time = float(scan_wait) if scan_wait else 0.25
|
|
180
|
+
|
|
181
|
+
for i in range(1, 255):
|
|
182
|
+
ip = f"{ip_stub}.{i}"
|
|
183
|
+
try:
|
|
184
|
+
# Try to connect to ZPL port (9100)
|
|
185
|
+
import socket
|
|
186
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
187
|
+
sock.settimeout(wait_time)
|
|
188
|
+
result = sock.connect_ex((ip, 9100))
|
|
189
|
+
sock.close()
|
|
190
|
+
|
|
191
|
+
if result == 0:
|
|
192
|
+
# Port is open, try to get printer info
|
|
193
|
+
model = "Unknown"
|
|
194
|
+
serial = "Unknown"
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Query printer for model and serial
|
|
198
|
+
printer = zdcm.ZebraPrinter(ip)
|
|
199
|
+
config = printer.get_configuration()
|
|
200
|
+
|
|
201
|
+
# Parse model from config
|
|
202
|
+
if "MODEL" in config:
|
|
203
|
+
for line in config.split('\n'):
|
|
204
|
+
if "MODEL" in line.upper():
|
|
205
|
+
parts = line.split(':')
|
|
206
|
+
if len(parts) > 1:
|
|
207
|
+
model = parts[1].strip()
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
# Parse serial from config
|
|
211
|
+
if "SERIAL" in config.upper():
|
|
212
|
+
for line in config.split('\n'):
|
|
213
|
+
if "SERIAL" in line.upper():
|
|
214
|
+
parts = line.split(':')
|
|
215
|
+
if len(parts) > 1:
|
|
216
|
+
serial = parts[1].strip()
|
|
217
|
+
break
|
|
218
|
+
except Exception:
|
|
219
|
+
pass # Use defaults if we can't query printer
|
|
220
|
+
|
|
221
|
+
if ip not in self.printers['labs'][lab]["printers"]:
|
|
222
|
+
# The label formats set here are the installed defaults
|
|
223
|
+
self.printers['labs'][lab]["printers"][ip] = {
|
|
224
|
+
"ip_address": ip,
|
|
225
|
+
"printer_name": None, # User can set friendly name later
|
|
226
|
+
"lab_location": None, # User can set location later
|
|
227
|
+
"manufacturer": "zebra",
|
|
228
|
+
"model": model,
|
|
229
|
+
"serial": serial,
|
|
230
|
+
"label_zpl_styles": ["tube_2inX1in", "plate_1inX0.25in", "tube_2inX0.3in"],
|
|
231
|
+
"default_label_style": "tube_2inX1in", # Default to first style
|
|
232
|
+
"print_method": "socket",
|
|
233
|
+
"arp_data": "",
|
|
234
|
+
"notes": ""
|
|
235
|
+
}
|
|
236
|
+
except Exception:
|
|
237
|
+
pass # Skip unreachable IPs
|
|
191
238
|
|
|
192
239
|
self.save_printer_json(self.printers_filename, relative=False)
|
|
193
240
|
|
|
@@ -274,15 +321,18 @@ class zpl:
|
|
|
274
321
|
|
|
275
322
|
def clear_printers_json(self, json_file: str = "/etc/printer_config.json") -> None:
|
|
276
323
|
"""
|
|
277
|
-
Reset printers JSON to empty minimal structure.
|
|
324
|
+
Reset printers JSON to empty minimal v2.0.0 structure.
|
|
278
325
|
|
|
279
326
|
Args:
|
|
280
327
|
json_file: Path to the config file (relative to package)
|
|
281
328
|
"""
|
|
282
329
|
json_path = Path(str(files('zebra_day'))) / json_file.lstrip('/')
|
|
283
330
|
|
|
284
|
-
# Write empty config
|
|
285
|
-
empty_config = {
|
|
331
|
+
# Write empty config with v2 schema
|
|
332
|
+
empty_config = {
|
|
333
|
+
"schema_version": "2.0.0",
|
|
334
|
+
"labs": {}
|
|
335
|
+
}
|
|
286
336
|
json_path.parent.mkdir(parents=True, exist_ok=True)
|
|
287
337
|
with open(json_path, 'w') as f:
|
|
288
338
|
json.dump(empty_config, f, indent=4)
|
|
@@ -313,22 +363,24 @@ class zpl:
|
|
|
313
363
|
|
|
314
364
|
|
|
315
365
|
|
|
316
|
-
def get_valid_label_styles_for_lab(self,lab=None):
|
|
366
|
+
def get_valid_label_styles_for_lab(self, lab=None):
|
|
317
367
|
"""
|
|
318
|
-
|
|
319
|
-
being requested for use in printing to some printer
|
|
320
|
-
was 'allowed' by checking with that printers printer json
|
|
321
|
-
for the array of valid templates.
|
|
368
|
+
Get all unique label styles available for printers in a lab.
|
|
322
369
|
|
|
323
|
-
|
|
370
|
+
The intention for this method was to confirm a template
|
|
371
|
+
being requested for use in printing to some printer
|
|
372
|
+
was 'allowed' by checking with that printers printer json
|
|
373
|
+
for the array of valid templates.
|
|
324
374
|
|
|
375
|
+
This was a huge PITA in testing, could be re-enabled at some point.
|
|
325
376
|
It is used once, but prints a warning only.
|
|
326
377
|
"""
|
|
327
|
-
|
|
328
378
|
unique_labels = set()
|
|
329
379
|
|
|
330
|
-
|
|
331
|
-
|
|
380
|
+
# Access printers via nested 'printers' key (v2 schema)
|
|
381
|
+
lab_printers = self.printers['labs'][lab].get('printers', {})
|
|
382
|
+
for printer_id, printer_data in lab_printers.items():
|
|
383
|
+
for style in printer_data.get('label_zpl_styles', []):
|
|
332
384
|
unique_labels.add(style)
|
|
333
385
|
|
|
334
386
|
result = list(unique_labels)
|
|
@@ -410,39 +462,43 @@ class zpl:
|
|
|
410
462
|
def print_zpl(self, lab=None, printer_name=None, uid_barcode='', alt_a='', alt_b='', alt_c='', alt_d='', alt_e='', alt_f='', label_zpl_style=None, client_ip='pkg', print_n=1, zpl_content=None):
|
|
411
463
|
"""
|
|
412
464
|
The main print method. Accepts info to determine the desired
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
lab = top level key in self.printers['labs']
|
|
417
|
-
printer_name = key for printer info (ie: ip_address) needed
|
|
418
|
-
to satisfy print requests.
|
|
419
|
-
label_zpl_style = template code, see above for addl deets
|
|
420
|
-
client_ip = optional, this is logged with print request info
|
|
421
|
-
print_n = integer, > 0
|
|
422
|
-
zpl_content = DO NOT USE -- hacky way to directly pass a zpl
|
|
423
|
-
string to a printer. to do: write a cleaner
|
|
424
|
-
string+ip method of printing.
|
|
425
|
-
"""
|
|
465
|
+
printer IP and to request the desired ZPL string to be sent
|
|
466
|
+
to the printer.
|
|
426
467
|
|
|
468
|
+
Args:
|
|
469
|
+
lab: top level key in self.printers['labs']
|
|
470
|
+
printer_name: key for printer info (ie: ip_address) needed
|
|
471
|
+
to satisfy print requests.
|
|
472
|
+
label_zpl_style: template code, see above for addl deets
|
|
473
|
+
client_ip: optional, this is logged with print request info
|
|
474
|
+
print_n: integer, > 0
|
|
475
|
+
zpl_content: DO NOT USE -- hacky way to directly pass a zpl
|
|
476
|
+
string to a printer. to do: write a cleaner
|
|
477
|
+
string+ip method of printing.
|
|
478
|
+
"""
|
|
427
479
|
if print_n < 1:
|
|
428
480
|
raise Exception(f"\n\nprint_n < 1 , specified {print_n}")
|
|
429
481
|
|
|
430
|
-
rec_date = str(datetime.datetime.now()).replace(' ','_')
|
|
482
|
+
rec_date = str(datetime.datetime.now()).replace(' ', '_')
|
|
431
483
|
print_n = int(print_n)
|
|
432
484
|
|
|
433
|
-
if printer_name in ['','None',None] and lab in [None,'','None']:
|
|
485
|
+
if printer_name in ['', 'None', None] and lab in [None, '', 'None']:
|
|
434
486
|
raise Exception(f"lab and printer_name are both required to route a zebra print request, the following was what was received: lab:{lab} & printer_name:{printer_name}")
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
487
|
+
|
|
488
|
+
# Access printer via nested 'printers' key (v2 schema)
|
|
489
|
+
printer_data = self.printers['labs'][lab]['printers'][printer_name]
|
|
490
|
+
|
|
491
|
+
if label_zpl_style in [None, '', 'None']:
|
|
492
|
+
# Use default_label_style if set, otherwise fall back to first in list
|
|
493
|
+
label_zpl_style = printer_data.get('default_label_style') or printer_data['label_zpl_styles'][0]
|
|
494
|
+
elif label_zpl_style not in printer_data['label_zpl_styles']:
|
|
439
495
|
_log.warning(
|
|
440
496
|
"ZPL style '%s' is not valid for %s/%s. Valid styles: %s",
|
|
441
497
|
label_zpl_style, lab, printer_name,
|
|
442
|
-
|
|
498
|
+
printer_data['label_zpl_styles']
|
|
443
499
|
)
|
|
444
500
|
|
|
445
|
-
printer_ip =
|
|
501
|
+
printer_ip = printer_data["ip_address"]
|
|
446
502
|
|
|
447
503
|
zpl_string = ''
|
|
448
504
|
if zpl_content in [None]:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{% extends "modern/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Config Backups - Zebra Day{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="page-header">
|
|
7
|
+
<h1 class="page-title">Configuration Backups</h1>
|
|
8
|
+
<div class="header-actions">
|
|
9
|
+
<a href="/config" class="btn btn-outline"><i class="fas fa-arrow-left"></i> Back</a>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="card">
|
|
14
|
+
<div class="card-header">
|
|
15
|
+
<h3 class="card-title"><i class="fas fa-history"></i> Saved Configuration Files</h3>
|
|
16
|
+
<span class="badge">{{ backup_files | length }} backups</span>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<p class="text-muted mb-md" style="padding: 0 1rem;">
|
|
20
|
+
To restore a backup, download the file, open in a text editor, copy the contents,
|
|
21
|
+
then paste into the <a href="/config/edit">config editor</a> and save.
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
{% if backup_files %}
|
|
25
|
+
<div class="table-container">
|
|
26
|
+
<table class="table">
|
|
27
|
+
<thead>
|
|
28
|
+
<tr>
|
|
29
|
+
<th>Filename</th>
|
|
30
|
+
<th>Actions</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
{% for file in backup_files %}
|
|
35
|
+
<tr>
|
|
36
|
+
<td>
|
|
37
|
+
<i class="fas fa-file-code text-muted"></i>
|
|
38
|
+
{{ file }}
|
|
39
|
+
</td>
|
|
40
|
+
<td>
|
|
41
|
+
<a href="/etc/old_printer_config/{{ file }}" class="btn btn-sm btn-outline" download>
|
|
42
|
+
<i class="fas fa-download"></i> Download
|
|
43
|
+
</a>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>
|
|
46
|
+
{% endfor %}
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
</div>
|
|
50
|
+
{% else %}
|
|
51
|
+
<div class="empty-state">
|
|
52
|
+
<i class="fas fa-history"></i>
|
|
53
|
+
<p>No backup files found</p>
|
|
54
|
+
<small class="text-muted">Backups are created automatically when you save configuration changes</small>
|
|
55
|
+
</div>
|
|
56
|
+
{% endif %}
|
|
57
|
+
</div>
|
|
58
|
+
{% endblock %}
|
|
59
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{% extends "modern/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ title }} - Zebra Day{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="page-header">
|
|
7
|
+
<h1 class="page-title">{{ title }}</h1>
|
|
8
|
+
<div class="header-actions">
|
|
9
|
+
<a href="/config" class="btn btn-outline"><i class="fas fa-arrow-left"></i> Back</a>
|
|
10
|
+
{% if mode == 'edit' %}
|
|
11
|
+
<button type="button" class="btn btn-outline" onclick="resetConfig()"><i class="fas fa-undo"></i> Reset</button>
|
|
12
|
+
<button type="button" class="btn btn-outline" onclick="clearConfig()"><i class="fas fa-trash"></i> Clear</button>
|
|
13
|
+
{% endif %}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{% if error_msg %}
|
|
18
|
+
<div class="alert alert-error mb-lg">
|
|
19
|
+
<i class="fas fa-exclamation-circle"></i> {{ error_msg }}
|
|
20
|
+
</div>
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
<div class="card">
|
|
24
|
+
<div class="card-header">
|
|
25
|
+
<h3 class="card-title"><i class="fas fa-file-code"></i> Printer Configuration JSON</h3>
|
|
26
|
+
{% if mode == 'edit' %}
|
|
27
|
+
<span class="badge badge-warning">Editing</span>
|
|
28
|
+
{% else %}
|
|
29
|
+
<span class="badge badge-info">Read Only</span>
|
|
30
|
+
{% endif %}
|
|
31
|
+
</div>
|
|
32
|
+
<form method="post" action="/config/save">
|
|
33
|
+
<div class="form-group">
|
|
34
|
+
<textarea name="json_data" id="json-editor" class="form-control code-editor" rows="30" {% if mode != 'edit' %}readonly{% endif %}>{{ config_json }}</textarea>
|
|
35
|
+
</div>
|
|
36
|
+
{% if mode == 'edit' %}
|
|
37
|
+
<div class="form-actions">
|
|
38
|
+
<button type="submit" class="btn btn-primary" onclick="showLoading('Saving configuration...')">
|
|
39
|
+
<i class="fas fa-save"></i> Save Configuration
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
{% endif %}
|
|
43
|
+
</form>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<style>
|
|
47
|
+
.code-editor {
|
|
48
|
+
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
|
49
|
+
font-size: 0.875rem;
|
|
50
|
+
line-height: 1.5;
|
|
51
|
+
background: var(--color-gray-700);
|
|
52
|
+
border: 1px solid var(--color-gray-600);
|
|
53
|
+
padding: 1rem;
|
|
54
|
+
resize: vertical;
|
|
55
|
+
min-height: 400px;
|
|
56
|
+
}
|
|
57
|
+
.code-editor:read-only {
|
|
58
|
+
background: var(--color-gray-800);
|
|
59
|
+
cursor: default;
|
|
60
|
+
}
|
|
61
|
+
.form-actions {
|
|
62
|
+
display: flex;
|
|
63
|
+
gap: 1rem;
|
|
64
|
+
padding: 1rem;
|
|
65
|
+
border-top: 1px solid var(--color-gray-600);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
68
|
+
|
|
69
|
+
<script>
|
|
70
|
+
async function resetConfig() {
|
|
71
|
+
if (confirm('Reset configuration to template defaults? This will backup the current config.')) {
|
|
72
|
+
showLoading('Resetting configuration...');
|
|
73
|
+
window.location.href = '/config/reset';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function clearConfig() {
|
|
78
|
+
if (confirm('Clear all printer configuration? This will backup the current config.')) {
|
|
79
|
+
showLoading('Clearing configuration...');
|
|
80
|
+
window.location.href = '/config/clear';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Auto-format JSON on blur
|
|
85
|
+
document.getElementById('json-editor')?.addEventListener('blur', function() {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(this.value);
|
|
88
|
+
this.value = JSON.stringify(parsed, null, 4);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Invalid JSON, leave as-is
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
{% endblock %}
|
|
95
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{% extends "modern/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}New Configuration - Zebra Day{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="page-header">
|
|
7
|
+
<h1 class="page-title">Build New Configuration</h1>
|
|
8
|
+
<div class="header-actions">
|
|
9
|
+
<a href="/config" class="btn btn-outline"><i class="fas fa-arrow-left"></i> Back</a>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="grid grid-2">
|
|
14
|
+
<!-- Network Scan -->
|
|
15
|
+
<div class="card">
|
|
16
|
+
<div class="card-header">
|
|
17
|
+
<h3 class="card-title"><i class="fas fa-wifi"></i> Scan Network for Printers</h3>
|
|
18
|
+
</div>
|
|
19
|
+
<form action="/config/scan" method="get">
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<label class="form-label">IP Range Prefix</label>
|
|
22
|
+
<input type="text" name="ip_stub" class="form-control" value="{{ ip_root }}" placeholder="192.168.1">
|
|
23
|
+
<small class="text-muted">Enter the first three octets of your network</small>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="form-group">
|
|
26
|
+
<label class="form-label">Lab Name</label>
|
|
27
|
+
<input type="text" name="lab" class="form-control" value="scan-results" placeholder="Enter lab name">
|
|
28
|
+
<small class="text-muted">Printers found will be added to this lab</small>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="form-group">
|
|
31
|
+
<label class="form-label">Scan Wait Time</label>
|
|
32
|
+
<select name="scan_wait" class="form-control">
|
|
33
|
+
<option value="0.15">Fast (0.15s)</option>
|
|
34
|
+
<option value="0.25" selected>Normal (0.25s)</option>
|
|
35
|
+
<option value="0.5">Slow (0.5s)</option>
|
|
36
|
+
<option value="1.0">Very Slow (1.0s)</option>
|
|
37
|
+
</select>
|
|
38
|
+
<small class="text-muted">Longer wait times find more printers on slow networks</small>
|
|
39
|
+
</div>
|
|
40
|
+
<button type="submit" class="btn btn-primary" onclick="showLoading('Scanning network for Zebra printers...')">
|
|
41
|
+
<i class="fas fa-search"></i> Start Network Scan
|
|
42
|
+
</button>
|
|
43
|
+
</form>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Quick Actions -->
|
|
47
|
+
<div class="card">
|
|
48
|
+
<div class="card-header">
|
|
49
|
+
<h3 class="card-title"><i class="fas fa-bolt"></i> Quick Actions</h3>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="quick-actions">
|
|
52
|
+
<a href="/config/reset" class="action-card" onclick="return confirm('Reset config to template defaults?')">
|
|
53
|
+
<i class="fas fa-undo"></i>
|
|
54
|
+
<div>
|
|
55
|
+
<strong>Reset to Template</strong>
|
|
56
|
+
<small class="text-muted d-block">Restore default configuration</small>
|
|
57
|
+
</div>
|
|
58
|
+
</a>
|
|
59
|
+
<a href="/config/clear" class="action-card" onclick="return confirm('Clear all printer configuration?')">
|
|
60
|
+
<i class="fas fa-trash"></i>
|
|
61
|
+
<div>
|
|
62
|
+
<strong>Clear Configuration</strong>
|
|
63
|
+
<small class="text-muted d-block">Remove all printers</small>
|
|
64
|
+
</div>
|
|
65
|
+
</a>
|
|
66
|
+
<a href="/config/edit" class="action-card">
|
|
67
|
+
<i class="fas fa-edit"></i>
|
|
68
|
+
<div>
|
|
69
|
+
<strong>Manual Edit</strong>
|
|
70
|
+
<small class="text-muted d-block">Edit JSON directly</small>
|
|
71
|
+
</div>
|
|
72
|
+
</a>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<hr class="my-md">
|
|
76
|
+
|
|
77
|
+
<h4 class="mb-md">Existing Labs</h4>
|
|
78
|
+
{% if labs %}
|
|
79
|
+
<div class="grid grid-3">
|
|
80
|
+
{% for lab in labs %}
|
|
81
|
+
<a href="/printers/{{ lab }}" class="action-card">
|
|
82
|
+
<i class="fas fa-building"></i>
|
|
83
|
+
<strong>{{ lab }}</strong>
|
|
84
|
+
</a>
|
|
85
|
+
{% endfor %}
|
|
86
|
+
</div>
|
|
87
|
+
{% else %}
|
|
88
|
+
<p class="text-muted">No labs configured yet. Run a network scan to discover printers.</p>
|
|
89
|
+
{% endif %}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
{% endblock %}
|
|
93
|
+
|
|
@@ -116,12 +116,16 @@
|
|
|
116
116
|
|
|
117
117
|
printerSelect.innerHTML = '<option value="">Select a printer...</option>';
|
|
118
118
|
|
|
119
|
-
if (lab && labsDict[lab]) {
|
|
120
|
-
|
|
119
|
+
if (lab && labsDict[lab] && labsDict[lab].printers) {
|
|
120
|
+
// v2 schema: printers are nested under 'printers' key
|
|
121
|
+
var printers = labsDict[lab].printers;
|
|
122
|
+
for (var printerId in printers) {
|
|
123
|
+
var printerInfo = printers[printerId];
|
|
121
124
|
var option = document.createElement('option');
|
|
122
|
-
option.value =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
option.value = printerId;
|
|
126
|
+
// Display friendly name if available, otherwise use ID
|
|
127
|
+
option.text = printerInfo.printer_name || printerId;
|
|
128
|
+
if (printerId === selectedPrinter) {
|
|
125
129
|
option.selected = true;
|
|
126
130
|
}
|
|
127
131
|
printerSelect.appendChild(option);
|