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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
<div class="page-header">
|
|
7
7
|
<h1 class="page-title">{{ printer_name }}</h1>
|
|
8
|
-
<p class="page-subtitle">Printer details for {{ lab }}</p>
|
|
8
|
+
<p class="page-subtitle">Printer details for {{ lab_name | default(lab) }}</p>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<!-- Breadcrumb -->
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<span class="text-muted"> / </span>
|
|
15
15
|
<a href="/printers">Printers</a>
|
|
16
16
|
<span class="text-muted"> / </span>
|
|
17
|
-
<a href="/printers/{{ lab }}">{{ lab }}</a>
|
|
17
|
+
<a href="/printers/{{ lab }}">{{ lab_name | default(lab) }}</a>
|
|
18
18
|
<span class="text-muted"> / </span>
|
|
19
19
|
<span>{{ printer_name }}</span>
|
|
20
20
|
</div>
|
|
@@ -32,53 +32,149 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<table class="table" style="background: transparent; box-shadow: none;">
|
|
34
34
|
<tbody>
|
|
35
|
-
{% for key, value in printer_info.items() %}
|
|
36
35
|
<tr>
|
|
37
|
-
<td style="width: 40%;"><strong>
|
|
36
|
+
<td style="width: 40%;"><strong>Printer ID</strong></td>
|
|
37
|
+
<td>{{ printer_id }}</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<td><strong>Display Name</strong></td>
|
|
41
|
+
<td>{{ printer_info.printer_name | default('Not set', true) }}</td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td><strong>Location</strong></td>
|
|
45
|
+
<td>{{ printer_info.lab_location | default('Not set', true) }}</td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td><strong>Manufacturer</strong></td>
|
|
49
|
+
<td>{{ printer_info.manufacturer | default('zebra') | title }}</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr>
|
|
52
|
+
<td><strong>Model</strong></td>
|
|
53
|
+
<td>{{ printer_info.model | default('Unknown') }}</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<tr>
|
|
56
|
+
<td><strong>Serial</strong></td>
|
|
57
|
+
<td>{{ printer_info.serial | default('Unknown') }}</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr>
|
|
60
|
+
<td><strong>IP Address</strong></td>
|
|
38
61
|
<td>
|
|
39
|
-
{% if
|
|
40
|
-
<a href="http://{{
|
|
41
|
-
{% elif key == 'status' %}
|
|
42
|
-
{% if value == 'online' %}
|
|
43
|
-
<span class="badge badge-success">Online</span>
|
|
62
|
+
{% if printer_info.ip_address != 'dl_png' %}
|
|
63
|
+
<a href="http://{{ printer_info.ip_address }}" target="_blank">{{ printer_info.ip_address }}</a>
|
|
44
64
|
{% else %}
|
|
45
|
-
|
|
65
|
+
{{ printer_info.ip_address }}
|
|
46
66
|
{% endif %}
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
<tr>
|
|
70
|
+
<td><strong>Print Method</strong></td>
|
|
71
|
+
<td>{{ printer_info.print_method | default('socket') }}</td>
|
|
72
|
+
</tr>
|
|
73
|
+
<tr>
|
|
74
|
+
<td><strong>Label Styles</strong></td>
|
|
75
|
+
<td>
|
|
76
|
+
{% for style in printer_info.label_zpl_styles %}
|
|
49
77
|
<span class="badge badge-primary">{{ style }}</span>
|
|
50
78
|
{% endfor %}
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
<tr>
|
|
82
|
+
<td><strong>Default Label Style</strong></td>
|
|
83
|
+
<td>
|
|
84
|
+
{% if printer_info.default_label_style %}
|
|
85
|
+
<span class="badge badge-success">{{ printer_info.default_label_style }}</span>
|
|
51
86
|
{% else %}
|
|
52
|
-
{{
|
|
87
|
+
<span class="text-muted">{{ printer_info.label_zpl_styles[0] if printer_info.label_zpl_styles else 'None' }} (first in list)</span>
|
|
53
88
|
{% endif %}
|
|
54
89
|
</td>
|
|
55
90
|
</tr>
|
|
56
|
-
{%
|
|
91
|
+
{% if printer_info.notes %}
|
|
92
|
+
<tr>
|
|
93
|
+
<td><strong>Notes</strong></td>
|
|
94
|
+
<td>{{ printer_info.notes }}</td>
|
|
95
|
+
</tr>
|
|
96
|
+
{% endif %}
|
|
57
97
|
</tbody>
|
|
58
98
|
</table>
|
|
59
99
|
</div>
|
|
60
|
-
|
|
61
|
-
<!--
|
|
100
|
+
|
|
101
|
+
<!-- Edit Printer Settings -->
|
|
62
102
|
<div class="card">
|
|
63
103
|
<div class="card-header">
|
|
64
|
-
<h3 class="card-title">
|
|
65
|
-
</div>
|
|
66
|
-
<div class="quick-actions">
|
|
67
|
-
<a href="/print?lab={{ lab }}&printer={{ printer_name }}" class="action-card">
|
|
68
|
-
<i class="fas fa-print"></i>
|
|
69
|
-
<div>
|
|
70
|
-
<strong>Print Label</strong>
|
|
71
|
-
<small class="text-muted d-block">Send a print request</small>
|
|
72
|
-
</div>
|
|
73
|
-
</a>
|
|
74
|
-
<a href="/templates" class="action-card">
|
|
75
|
-
<i class="fas fa-file-code"></i>
|
|
76
|
-
<div>
|
|
77
|
-
<strong>View Templates</strong>
|
|
78
|
-
<small class="text-muted d-block">Available label styles</small>
|
|
79
|
-
</div>
|
|
80
|
-
</a>
|
|
104
|
+
<h3 class="card-title">Edit Settings</h3>
|
|
81
105
|
</div>
|
|
106
|
+
<form id="printer-edit-form" class="form-group">
|
|
107
|
+
<div class="form-group">
|
|
108
|
+
<label class="form-label">Display Name</label>
|
|
109
|
+
<input type="text" name="printer_name" class="form-control"
|
|
110
|
+
value="{{ printer_info.printer_name | default('', true) }}"
|
|
111
|
+
placeholder="e.g., Lab 3 - Bench A">
|
|
112
|
+
<small class="text-muted">User-friendly name for this printer</small>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="form-group">
|
|
115
|
+
<label class="form-label">Location</label>
|
|
116
|
+
{% if available_locations %}
|
|
117
|
+
<select name="lab_location" class="form-control">
|
|
118
|
+
<option value="">-- Select Location --</option>
|
|
119
|
+
{% for loc in available_locations %}
|
|
120
|
+
<option value="{{ loc }}" {% if printer_info.lab_location == loc %}selected{% endif %}>{{ loc }}</option>
|
|
121
|
+
{% endfor %}
|
|
122
|
+
</select>
|
|
123
|
+
{% else %}
|
|
124
|
+
<input type="text" name="lab_location" class="form-control"
|
|
125
|
+
value="{{ printer_info.lab_location | default('', true) }}"
|
|
126
|
+
placeholder="No locations defined for this lab">
|
|
127
|
+
<small class="text-muted">Define locations at the lab level first</small>
|
|
128
|
+
{% endif %}
|
|
129
|
+
</div>
|
|
130
|
+
<div class="form-group">
|
|
131
|
+
<label class="form-label">Default Label Style</label>
|
|
132
|
+
<select name="default_label_style" class="form-control">
|
|
133
|
+
<option value="">-- Use first in list --</option>
|
|
134
|
+
{% for style in printer_info.label_zpl_styles %}
|
|
135
|
+
<option value="{{ style }}" {% if printer_info.default_label_style == style %}selected{% endif %}>{{ style }}</option>
|
|
136
|
+
{% endfor %}
|
|
137
|
+
</select>
|
|
138
|
+
<small class="text-muted">Style used when printing without specifying a template</small>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="form-group">
|
|
141
|
+
<label class="form-label">Notes</label>
|
|
142
|
+
<textarea name="notes" class="form-control" rows="2" placeholder="Optional notes">{{ printer_info.notes | default('', true) }}</textarea>
|
|
143
|
+
</div>
|
|
144
|
+
<button type="submit" class="btn btn-primary">
|
|
145
|
+
<i class="fas fa-save"></i> Save Changes
|
|
146
|
+
</button>
|
|
147
|
+
</form>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Quick Actions -->
|
|
152
|
+
<div class="card mt-lg">
|
|
153
|
+
<div class="card-header">
|
|
154
|
+
<h3 class="card-title">Quick Actions</h3>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="grid grid-3">
|
|
157
|
+
<a href="/print?lab={{ lab }}&printer={{ printer_id }}" class="action-card">
|
|
158
|
+
<i class="fas fa-print"></i>
|
|
159
|
+
<div>
|
|
160
|
+
<strong>Print Label</strong>
|
|
161
|
+
<small class="text-muted d-block">Send a print request</small>
|
|
162
|
+
</div>
|
|
163
|
+
</a>
|
|
164
|
+
<a href="/templates" class="action-card">
|
|
165
|
+
<i class="fas fa-file-code"></i>
|
|
166
|
+
<div>
|
|
167
|
+
<strong>View Templates</strong>
|
|
168
|
+
<small class="text-muted d-block">Available label styles</small>
|
|
169
|
+
</div>
|
|
170
|
+
</a>
|
|
171
|
+
<a href="/printers/{{ lab }}" class="action-card">
|
|
172
|
+
<i class="fas fa-arrow-left"></i>
|
|
173
|
+
<div>
|
|
174
|
+
<strong>Back to Lab</strong>
|
|
175
|
+
<small class="text-muted d-block">View all printers</small>
|
|
176
|
+
</div>
|
|
177
|
+
</a>
|
|
82
178
|
</div>
|
|
83
179
|
</div>
|
|
84
180
|
|
|
@@ -103,7 +199,7 @@
|
|
|
103
199
|
<p class="text-muted mb-lg">Send a test label to verify the printer is working correctly.</p>
|
|
104
200
|
<div class="grid grid-3">
|
|
105
201
|
{% for style in printer_info.label_zpl_styles[:6] %}
|
|
106
|
-
<a href="/print?lab={{ lab }}&printer={{
|
|
202
|
+
<a href="/print?lab={{ lab }}&printer={{ printer_id }}&template={{ style }}&test=1" class="action-card">
|
|
107
203
|
<i class="fas fa-tag"></i>
|
|
108
204
|
<div>
|
|
109
205
|
<strong>{{ style }}</strong>
|
|
@@ -113,5 +209,36 @@
|
|
|
113
209
|
{% endfor %}
|
|
114
210
|
</div>
|
|
115
211
|
</div>
|
|
212
|
+
|
|
213
|
+
<script>
|
|
214
|
+
document.getElementById('printer-edit-form').addEventListener('submit', async (e) => {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
const form = e.target;
|
|
217
|
+
const data = {
|
|
218
|
+
printer_name: form.printer_name.value || null,
|
|
219
|
+
lab_location: form.lab_location.value || null,
|
|
220
|
+
default_label_style: form.default_label_style.value || null,
|
|
221
|
+
notes: form.notes.value || ''
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const response = await fetch('/api/v1/labs/{{ lab }}/printers/{{ printer_id }}', {
|
|
226
|
+
method: 'PATCH',
|
|
227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
228
|
+
body: JSON.stringify(data)
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (response.ok) {
|
|
232
|
+
showToast('success', 'Saved', 'Printer settings updated successfully');
|
|
233
|
+
setTimeout(() => window.location.reload(), 1000);
|
|
234
|
+
} else {
|
|
235
|
+
const err = await response.json();
|
|
236
|
+
showToast('error', 'Error', err.detail || 'Failed to save settings');
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
showToast('error', 'Error', 'Network error: ' + err.message);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
</script>
|
|
116
243
|
{% endblock %}
|
|
117
244
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{% if printers %}
|
|
33
33
|
<div class="card">
|
|
34
34
|
<div class="card-header">
|
|
35
|
-
<h3 class="card-title">Printers{% if lab %} in {{ lab }}{% endif %}</h3>
|
|
35
|
+
<h3 class="card-title">Printers{% if lab %} in {{ lab_name | default(lab) }}{% endif %}</h3>
|
|
36
36
|
<a href="/config?action=scan&lab={{ lab | default('') }}" class="btn btn-outline btn-sm">
|
|
37
37
|
<i class="fas fa-sync"></i> Rescan
|
|
38
38
|
</a>
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
<thead>
|
|
43
43
|
<tr>
|
|
44
44
|
<th>Printer</th>
|
|
45
|
+
<th>Location</th>
|
|
45
46
|
<th>IP Address</th>
|
|
46
47
|
<th>Status</th>
|
|
47
48
|
<th>Label Styles</th>
|
|
@@ -53,7 +54,17 @@
|
|
|
53
54
|
<tr>
|
|
54
55
|
<td>
|
|
55
56
|
<strong>{{ printer.name }}</strong><br>
|
|
56
|
-
<small class="text-muted">{{ printer.model | default('Unknown model') }}</small>
|
|
57
|
+
<small class="text-muted">{{ printer.manufacturer | default('zebra') | title }} {{ printer.model | default('Unknown model') }}</small>
|
|
58
|
+
{% if printer.id != printer.name %}
|
|
59
|
+
<br><small class="text-muted">ID: {{ printer.id }}</small>
|
|
60
|
+
{% endif %}
|
|
61
|
+
</td>
|
|
62
|
+
<td>
|
|
63
|
+
{% if printer.lab_location %}
|
|
64
|
+
<span class="badge badge-info">{{ printer.lab_location }}</span>
|
|
65
|
+
{% else %}
|
|
66
|
+
<span class="text-muted">—</span>
|
|
67
|
+
{% endif %}
|
|
57
68
|
</td>
|
|
58
69
|
<td>
|
|
59
70
|
{% if printer.ip_address != 'dl_png' %}
|
|
@@ -64,9 +75,9 @@
|
|
|
64
75
|
</td>
|
|
65
76
|
<td>
|
|
66
77
|
{% if printer.status == 'online' %}
|
|
67
|
-
<span class="badge badge-success">Online</span>
|
|
78
|
+
<span class="badge badge-success"><i class="fas fa-check-circle"></i> Online</span>
|
|
68
79
|
{% else %}
|
|
69
|
-
<span class="badge badge-error">Offline</span>
|
|
80
|
+
<span class="badge badge-error"><i class="fas fa-times-circle"></i> Offline</span>
|
|
70
81
|
{% endif %}
|
|
71
82
|
</td>
|
|
72
83
|
<td>
|
|
@@ -78,10 +89,10 @@
|
|
|
78
89
|
{% endif %}
|
|
79
90
|
</td>
|
|
80
91
|
<td>
|
|
81
|
-
<a href="/printers/{{ lab }}/{{ printer.
|
|
92
|
+
<a href="/printers/{{ lab }}/{{ printer.id }}" class="btn btn-sm btn-outline">
|
|
82
93
|
<i class="fas fa-info-circle"></i> Details
|
|
83
94
|
</a>
|
|
84
|
-
<a href="/print?lab={{ lab }}&printer={{ printer.
|
|
95
|
+
<a href="/print?lab={{ lab }}&printer={{ printer.id }}" class="btn btn-sm btn-primary">
|
|
85
96
|
<i class="fas fa-print"></i> Print
|
|
86
97
|
</a>
|
|
87
98
|
</td>
|
|
@@ -113,11 +113,14 @@
|
|
|
113
113
|
var lab = labSelect.value;
|
|
114
114
|
|
|
115
115
|
printerSelect.innerHTML = '<option value="">Select printer...</option>';
|
|
116
|
-
if (lab && labsDict[lab]) {
|
|
117
|
-
|
|
116
|
+
if (lab && labsDict[lab] && labsDict[lab].printers) {
|
|
117
|
+
// v2 schema: printers are nested under 'printers' key
|
|
118
|
+
var printers = labsDict[lab].printers;
|
|
119
|
+
for (var printerId in printers) {
|
|
120
|
+
var printerInfo = printers[printerId];
|
|
118
121
|
var option = document.createElement('option');
|
|
119
|
-
option.value =
|
|
120
|
-
option.text =
|
|
122
|
+
option.value = printerId;
|
|
123
|
+
option.text = printerInfo.printer_name || printerId;
|
|
121
124
|
printerSelect.appendChild(option);
|
|
122
125
|
}
|
|
123
126
|
}
|
zebra_day/web/app.py
CHANGED
|
@@ -141,11 +141,30 @@ def create_app(
|
|
|
141
141
|
return app
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
def get_default_cert_paths() -> tuple[Optional[Path], Optional[Path]]:
|
|
145
|
+
"""
|
|
146
|
+
Get default certificate paths from XDG config directory.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple of (cert_path, key_path) or (None, None) if not found.
|
|
150
|
+
"""
|
|
151
|
+
config_dir = xdg.get_config_dir()
|
|
152
|
+
cert_dir = config_dir / "certs"
|
|
153
|
+
cert_file = cert_dir / "server.crt"
|
|
154
|
+
key_file = cert_dir / "server.key"
|
|
155
|
+
|
|
156
|
+
if cert_file.exists() and key_file.exists():
|
|
157
|
+
return cert_file, key_file
|
|
158
|
+
return None, None
|
|
159
|
+
|
|
160
|
+
|
|
144
161
|
def run_server(
|
|
145
162
|
host: str = "0.0.0.0",
|
|
146
163
|
port: int = 8118,
|
|
147
164
|
reload: bool = False,
|
|
148
165
|
auth: Literal["none", "cognito"] = "none",
|
|
166
|
+
ssl_certfile: Optional[str] = None,
|
|
167
|
+
ssl_keyfile: Optional[str] = None,
|
|
149
168
|
):
|
|
150
169
|
"""
|
|
151
170
|
Run the FastAPI server using uvicorn.
|
|
@@ -155,17 +174,75 @@ def run_server(
|
|
|
155
174
|
port: Port to listen on
|
|
156
175
|
reload: Enable auto-reload for development
|
|
157
176
|
auth: Authentication mode - "none" (public) or "cognito" (AWS Cognito)
|
|
177
|
+
ssl_certfile: Path to SSL certificate file (PEM format)
|
|
178
|
+
ssl_keyfile: Path to SSL private key file (PEM format)
|
|
179
|
+
|
|
180
|
+
If ssl_certfile and ssl_keyfile are not provided, the server will:
|
|
181
|
+
1. Check SSL_CERT_PATH and SSL_KEY_PATH environment variables
|
|
182
|
+
2. Check for certificates in ~/.config/zebra_day/certs/
|
|
183
|
+
3. Fall back to HTTP with a warning if no certificates are found
|
|
158
184
|
"""
|
|
159
185
|
import uvicorn
|
|
160
186
|
|
|
161
187
|
# Store auth mode in environment for factory function
|
|
162
188
|
os.environ["ZEBRA_DAY_AUTH_MODE"] = auth
|
|
163
189
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
# Resolve SSL certificate paths
|
|
191
|
+
cert_path = ssl_certfile
|
|
192
|
+
key_path = ssl_keyfile
|
|
193
|
+
|
|
194
|
+
# Check environment variables if not provided
|
|
195
|
+
if not cert_path:
|
|
196
|
+
cert_path = os.environ.get("SSL_CERT_PATH")
|
|
197
|
+
if not key_path:
|
|
198
|
+
key_path = os.environ.get("SSL_KEY_PATH")
|
|
199
|
+
|
|
200
|
+
# Check default XDG paths if still not found
|
|
201
|
+
if not cert_path or not key_path:
|
|
202
|
+
default_cert, default_key = get_default_cert_paths()
|
|
203
|
+
if default_cert and default_key:
|
|
204
|
+
cert_path = str(default_cert)
|
|
205
|
+
key_path = str(default_key)
|
|
206
|
+
|
|
207
|
+
# Validate certificate files exist
|
|
208
|
+
use_ssl = False
|
|
209
|
+
if cert_path and key_path:
|
|
210
|
+
cert_exists = Path(cert_path).exists()
|
|
211
|
+
key_exists = Path(key_path).exists()
|
|
212
|
+
if cert_exists and key_exists:
|
|
213
|
+
use_ssl = True
|
|
214
|
+
_log.info("HTTPS enabled with certificates:")
|
|
215
|
+
_log.info(" Certificate: %s", cert_path)
|
|
216
|
+
_log.info(" Private key: %s", key_path)
|
|
217
|
+
else:
|
|
218
|
+
if not cert_exists:
|
|
219
|
+
_log.warning("SSL certificate not found: %s", cert_path)
|
|
220
|
+
if not key_exists:
|
|
221
|
+
_log.warning("SSL private key not found: %s", key_path)
|
|
222
|
+
_log.warning("Falling back to HTTP (insecure)")
|
|
223
|
+
else:
|
|
224
|
+
_log.warning(
|
|
225
|
+
"No SSL certificates configured. Running in HTTP mode (insecure). "
|
|
226
|
+
"For HTTPS, run: mkcert -install && mkcert -cert-file ~/.config/zebra_day/certs/server.crt "
|
|
227
|
+
"-key-file ~/.config/zebra_day/certs/server.key localhost 127.0.0.1 ::1"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Build uvicorn config
|
|
231
|
+
uvicorn_kwargs = {
|
|
232
|
+
"host": host,
|
|
233
|
+
"port": port,
|
|
234
|
+
"reload": reload,
|
|
235
|
+
"factory": True,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if use_ssl:
|
|
239
|
+
uvicorn_kwargs["ssl_certfile"] = cert_path
|
|
240
|
+
uvicorn_kwargs["ssl_keyfile"] = key_path
|
|
241
|
+
protocol = "https"
|
|
242
|
+
else:
|
|
243
|
+
protocol = "http"
|
|
244
|
+
|
|
245
|
+
_log.info("Starting server at %s://%s:%d", protocol, host, port)
|
|
246
|
+
|
|
247
|
+
uvicorn.run("zebra_day.web.app:create_app", **uvicorn_kwargs)
|
|
171
248
|
|
zebra_day/web/routers/api.py
CHANGED
|
@@ -39,17 +39,30 @@ class PrintResponse(BaseModel):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class PrinterInfo(BaseModel):
|
|
42
|
-
"""Printer information model."""
|
|
43
|
-
|
|
42
|
+
"""Printer information model (v2.0.0 schema)."""
|
|
43
|
+
id: str = Field(..., description="Printer identifier/key in JSON")
|
|
44
44
|
ip_address: str
|
|
45
|
+
printer_name: Optional[str] = Field(None, description="User-friendly display name")
|
|
46
|
+
lab_location: Optional[str] = Field(None, description="Location within the lab")
|
|
47
|
+
manufacturer: str = Field("zebra", description="Printer manufacturer")
|
|
45
48
|
model: str
|
|
46
49
|
serial: str
|
|
47
50
|
label_zpl_styles: List[str]
|
|
51
|
+
default_label_style: Optional[str] = Field(None, description="Default label style to use when none specified")
|
|
48
52
|
print_method: str
|
|
53
|
+
notes: Optional[str] = Field("", description="Optional notes")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class LabInfo(BaseModel):
|
|
57
|
+
"""Lab information model (v2.0.0 schema)."""
|
|
58
|
+
id: str = Field(..., description="Lab identifier/key in JSON")
|
|
59
|
+
lab_name: str = Field(..., description="Human-readable lab name")
|
|
60
|
+
available_locations: List[str] = Field(default_factory=list, description="Valid location options for printers")
|
|
61
|
+
printers: List[PrinterInfo]
|
|
49
62
|
|
|
50
63
|
|
|
51
64
|
class LabPrinters(BaseModel):
|
|
52
|
-
"""Lab and its printers."""
|
|
65
|
+
"""Lab and its printers (deprecated, use LabInfo)."""
|
|
53
66
|
lab: str
|
|
54
67
|
printers: List[PrinterInfo]
|
|
55
68
|
|
|
@@ -63,6 +76,44 @@ async def list_labs(request: Request) -> List[str]:
|
|
|
63
76
|
return list(zp.printers.get("labs", {}).keys())
|
|
64
77
|
|
|
65
78
|
|
|
79
|
+
@router.get("/labs/{lab}", response_model=LabInfo)
|
|
80
|
+
async def get_lab(request: Request, lab: str) -> LabInfo:
|
|
81
|
+
"""Get lab details including available locations and printers."""
|
|
82
|
+
zp = request.app.state.zp
|
|
83
|
+
labs = zp.printers.get("labs", {})
|
|
84
|
+
|
|
85
|
+
if lab not in labs:
|
|
86
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
87
|
+
|
|
88
|
+
lab_data = labs[lab]
|
|
89
|
+
lab_printers = lab_data.get("printers", {})
|
|
90
|
+
|
|
91
|
+
printers = []
|
|
92
|
+
for printer_id, info in lab_printers.items():
|
|
93
|
+
printers.append(
|
|
94
|
+
PrinterInfo(
|
|
95
|
+
id=printer_id,
|
|
96
|
+
ip_address=info.get("ip_address", ""),
|
|
97
|
+
printer_name=info.get("printer_name"),
|
|
98
|
+
lab_location=info.get("lab_location"),
|
|
99
|
+
manufacturer=info.get("manufacturer", "zebra"),
|
|
100
|
+
model=info.get("model", ""),
|
|
101
|
+
serial=info.get("serial", ""),
|
|
102
|
+
label_zpl_styles=info.get("label_zpl_styles", []),
|
|
103
|
+
default_label_style=info.get("default_label_style"),
|
|
104
|
+
print_method=info.get("print_method", ""),
|
|
105
|
+
notes=info.get("notes", ""),
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return LabInfo(
|
|
110
|
+
id=lab,
|
|
111
|
+
lab_name=lab_data.get("lab_name", lab),
|
|
112
|
+
available_locations=lab_data.get("available_locations", []),
|
|
113
|
+
printers=printers,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
66
117
|
@router.get("/labs/{lab}/printers", response_model=List[PrinterInfo])
|
|
67
118
|
async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
|
|
68
119
|
"""List all printers in a lab."""
|
|
@@ -72,16 +123,24 @@ async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
|
|
|
72
123
|
if lab not in labs:
|
|
73
124
|
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
74
125
|
|
|
126
|
+
# Access printers via nested 'printers' key (v2 schema)
|
|
127
|
+
lab_printers = labs[lab].get("printers", {})
|
|
128
|
+
|
|
75
129
|
printers = []
|
|
76
|
-
for
|
|
130
|
+
for printer_id, info in lab_printers.items():
|
|
77
131
|
printers.append(
|
|
78
132
|
PrinterInfo(
|
|
79
|
-
|
|
133
|
+
id=printer_id,
|
|
80
134
|
ip_address=info.get("ip_address", ""),
|
|
135
|
+
printer_name=info.get("printer_name"),
|
|
136
|
+
lab_location=info.get("lab_location"),
|
|
137
|
+
manufacturer=info.get("manufacturer", "zebra"),
|
|
81
138
|
model=info.get("model", ""),
|
|
82
139
|
serial=info.get("serial", ""),
|
|
83
140
|
label_zpl_styles=info.get("label_zpl_styles", []),
|
|
141
|
+
default_label_style=info.get("default_label_style"),
|
|
84
142
|
print_method=info.get("print_method", ""),
|
|
143
|
+
notes=info.get("notes", ""),
|
|
85
144
|
)
|
|
86
145
|
)
|
|
87
146
|
return printers
|
|
@@ -161,3 +220,94 @@ async def get_config(request: Request) -> Dict[str, Any]:
|
|
|
161
220
|
zp = request.app.state.zp
|
|
162
221
|
return zp.printers
|
|
163
222
|
|
|
223
|
+
|
|
224
|
+
# ----- Lab Settings Endpoints -----
|
|
225
|
+
|
|
226
|
+
class LabUpdateRequest(BaseModel):
|
|
227
|
+
"""Request model for updating lab settings."""
|
|
228
|
+
lab_name: Optional[str] = Field(None, description="Human-readable lab name")
|
|
229
|
+
available_locations: Optional[List[str]] = Field(None, description="List of valid locations")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class PrinterUpdateRequest(BaseModel):
|
|
233
|
+
"""Request model for updating printer settings."""
|
|
234
|
+
printer_name: Optional[str] = Field(None, description="User-friendly display name")
|
|
235
|
+
lab_location: Optional[str] = Field(None, description="Location within the lab")
|
|
236
|
+
notes: Optional[str] = Field(None, description="Optional notes")
|
|
237
|
+
label_zpl_styles: Optional[List[str]] = Field(None, description="Allowed ZPL styles")
|
|
238
|
+
default_label_style: Optional[str] = Field(None, description="Default label style to use when none specified")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@router.patch("/labs/{lab}", response_model=LabInfo)
|
|
242
|
+
async def update_lab(request: Request, lab: str, update: LabUpdateRequest) -> LabInfo:
|
|
243
|
+
"""Update lab settings (lab_name, available_locations)."""
|
|
244
|
+
zp = request.app.state.zp
|
|
245
|
+
labs = zp.printers.get("labs", {})
|
|
246
|
+
|
|
247
|
+
if lab not in labs:
|
|
248
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
249
|
+
|
|
250
|
+
lab_data = labs[lab]
|
|
251
|
+
|
|
252
|
+
if update.lab_name is not None:
|
|
253
|
+
lab_data["lab_name"] = update.lab_name
|
|
254
|
+
if update.available_locations is not None:
|
|
255
|
+
lab_data["available_locations"] = update.available_locations
|
|
256
|
+
|
|
257
|
+
# Save changes
|
|
258
|
+
zp.save_printer_json(zp.printers_filename, relative=False)
|
|
259
|
+
|
|
260
|
+
# Return updated lab info
|
|
261
|
+
return await get_lab(request, lab)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@router.patch("/labs/{lab}/printers/{printer_id}")
|
|
265
|
+
async def update_printer(
|
|
266
|
+
request: Request, lab: str, printer_id: str, update: PrinterUpdateRequest
|
|
267
|
+
) -> PrinterInfo:
|
|
268
|
+
"""Update printer settings (printer_name, lab_location, notes)."""
|
|
269
|
+
zp = request.app.state.zp
|
|
270
|
+
labs = zp.printers.get("labs", {})
|
|
271
|
+
|
|
272
|
+
if lab not in labs:
|
|
273
|
+
raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
|
|
274
|
+
|
|
275
|
+
lab_printers = labs[lab].get("printers", {})
|
|
276
|
+
if printer_id not in lab_printers:
|
|
277
|
+
raise HTTPException(status_code=404, detail=f"Printer '{printer_id}' not found in lab '{lab}'")
|
|
278
|
+
|
|
279
|
+
printer_data = lab_printers[printer_id]
|
|
280
|
+
|
|
281
|
+
if update.printer_name is not None:
|
|
282
|
+
printer_data["printer_name"] = update.printer_name if update.printer_name else None
|
|
283
|
+
if update.lab_location is not None:
|
|
284
|
+
printer_data["lab_location"] = update.lab_location if update.lab_location else None
|
|
285
|
+
if update.notes is not None:
|
|
286
|
+
printer_data["notes"] = update.notes
|
|
287
|
+
if update.label_zpl_styles is not None:
|
|
288
|
+
printer_data["label_zpl_styles"] = update.label_zpl_styles
|
|
289
|
+
if update.default_label_style is not None:
|
|
290
|
+
# Validate that the style exists in label_zpl_styles (if it's not empty string)
|
|
291
|
+
if update.default_label_style and update.default_label_style not in printer_data.get("label_zpl_styles", []):
|
|
292
|
+
raise HTTPException(
|
|
293
|
+
status_code=400,
|
|
294
|
+
detail=f"Default label style '{update.default_label_style}' must be one of: {printer_data.get('label_zpl_styles', [])}"
|
|
295
|
+
)
|
|
296
|
+
printer_data["default_label_style"] = update.default_label_style if update.default_label_style else None
|
|
297
|
+
|
|
298
|
+
# Save changes
|
|
299
|
+
zp.save_printer_json(zp.printers_filename, relative=False)
|
|
300
|
+
|
|
301
|
+
return PrinterInfo(
|
|
302
|
+
id=printer_id,
|
|
303
|
+
ip_address=printer_data.get("ip_address", ""),
|
|
304
|
+
printer_name=printer_data.get("printer_name"),
|
|
305
|
+
lab_location=printer_data.get("lab_location"),
|
|
306
|
+
manufacturer=printer_data.get("manufacturer", "zebra"),
|
|
307
|
+
model=printer_data.get("model", ""),
|
|
308
|
+
serial=printer_data.get("serial", ""),
|
|
309
|
+
label_zpl_styles=printer_data.get("label_zpl_styles", []),
|
|
310
|
+
default_label_style=printer_data.get("default_label_style"),
|
|
311
|
+
print_method=printer_data.get("print_method", ""),
|
|
312
|
+
notes=printer_data.get("notes", ""),
|
|
313
|
+
)
|