zebra-day 1.0.2__py3-none-any.whl → 2.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zebra_day/__init__.py +7 -2
- zebra_day/_version.py +1 -0
- zebra_day/cli/__init__.py +80 -30
- zebra_day/cli/cognito.py +15 -9
- zebra_day/cli/gui.py +101 -13
- zebra_day/cli/printer.py +34 -27
- zebra_day/cli/template.py +19 -15
- zebra_day/cmd_mgr.py +3 -6
- zebra_day/docs/gx420d-gx430d-ug-en.pdf +0 -0
- zebra_day/docs/hardware_config_guide.md +149 -0
- zebra_day/docs/programatic_guide.md +181 -0
- zebra_day/docs/qln420_zebra_manual.pdf +0 -0
- zebra_day/docs/uid_screed_light.md +38 -0
- zebra_day/docs/zd620-zd420-ug-en.pdf +0 -0
- zebra_day/docs/zebra_day_ui_guide.md +194 -0
- zebra_day/etc/printer_config.json +5 -11
- zebra_day/etc/printer_config.template.json +5 -11
- zebra_day/etc/tmp_printers120.json +10 -0
- zebra_day/etc/tmp_printers139.json +10 -0
- zebra_day/etc/tmp_printers145.json +10 -0
- zebra_day/etc/tmp_printers147.json +10 -0
- zebra_day/etc/tmp_printers207.json +10 -0
- zebra_day/etc/tmp_printers34.json +10 -0
- zebra_day/etc/tmp_printers389.json +10 -0
- zebra_day/etc/tmp_printers398.json +10 -0
- zebra_day/etc/tmp_printers437.json +10 -0
- zebra_day/etc/tmp_printers439.json +10 -0
- zebra_day/etc/tmp_printers440.json +10 -0
- zebra_day/etc/tmp_printers469.json +10 -0
- zebra_day/etc/tmp_printers485.json +10 -0
- zebra_day/etc/tmp_printers508.json +10 -0
- zebra_day/etc/tmp_printers531.json +10 -0
- zebra_day/etc/tmp_printers540.json +10 -0
- zebra_day/etc/tmp_printers542.json +10 -0
- zebra_day/etc/tmp_printers543.json +10 -0
- zebra_day/etc/tmp_printers552.json +10 -0
- zebra_day/etc/tmp_printers715.json +10 -0
- zebra_day/etc/tmp_printers835.json +10 -0
- zebra_day/etc/tmp_printers842.json +10 -0
- zebra_day/etc/tmp_printers931.json +10 -0
- zebra_day/etc/tmp_printers969.json +10 -0
- zebra_day/etc/tmp_printers972.json +10 -0
- zebra_day/exceptions.py +1 -1
- zebra_day/files/blank_preview.png +0 -0
- zebra_day/files/corners_20cmX30cm_preview.png +0 -0
- zebra_day/files/corners_smallTube_preview.png +0 -0
- zebra_day/files/generic_2inX1in_preview.png +0 -0
- zebra_day/files/test_png_12020.png +0 -0
- zebra_day/files/test_png_12352.png +0 -0
- zebra_day/files/test_png_15472.png +0 -0
- zebra_day/files/test_png_24493.png +0 -0
- zebra_day/files/test_png_2897.png +0 -0
- zebra_day/files/test_png_30069.png +0 -0
- zebra_day/files/test_png_31690.png +0 -0
- zebra_day/files/test_png_33804.png +0 -0
- zebra_day/files/test_png_34737.png +0 -0
- zebra_day/files/test_png_4161.png +0 -0
- zebra_day/files/test_png_44748.png +0 -0
- zebra_day/files/test_png_4635.png +0 -0
- zebra_day/files/test_png_47791.png +0 -0
- zebra_day/files/test_png_47799.png +0 -0
- zebra_day/files/test_png_55588.png +0 -0
- zebra_day/files/test_png_56349.png +0 -0
- zebra_day/files/test_png_58809.png +0 -0
- zebra_day/files/test_png_5936.png +0 -0
- zebra_day/files/test_png_64110.png +0 -0
- zebra_day/files/test_png_64891.png +0 -0
- zebra_day/files/test_png_67242.png +0 -0
- zebra_day/files/test_png_69002.png +0 -0
- zebra_day/files/test_png_70065.png +0 -0
- zebra_day/files/test_png_72366.png +0 -0
- zebra_day/files/test_png_77793.png +0 -0
- zebra_day/files/test_png_89893.png +0 -0
- zebra_day/files/test_png_9572.png +0 -0
- zebra_day/files/tube_20mmX30mmA_preview.png +0 -0
- zebra_day/imgs/.hold +0 -0
- zebra_day/imgs/bar_ltpurp.png +0 -0
- zebra_day/imgs/bar_purp.png +0 -0
- zebra_day/imgs/bar_purp3.png +0 -0
- zebra_day/imgs/bar_red.png +0 -0
- zebra_day/imgs/legacy/UBC_gantt_chart.png +0 -0
- zebra_day/imgs/legacy/gx420d_network_config.png +0 -0
- zebra_day/imgs/legacy/gx420d_printer_config.png +0 -0
- zebra_day/imgs/legacy/ngrok.png +0 -0
- zebra_day/imgs/legacy/printer_details.png +0 -0
- zebra_day/imgs/legacy/quick_start_test_label.png +0 -0
- zebra_day/imgs/legacy/quick_start_test_label2.png +0 -0
- zebra_day/imgs/legacy/zd620_network_config.png +0 -0
- zebra_day/imgs/legacy/zd620_printer_config.png +0 -0
- zebra_day/imgs/legacy/zday_quick_gui.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_dog.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_flower.png +0 -0
- zebra_day/imgs/legacy/zebra_day_alt_css_main.png +0 -0
- zebra_day/imgs/legacy/zebra_day_available_zpl_templates.png +0 -0
- zebra_day/imgs/legacy/zebra_day_bkup_pconfig.png +0 -0
- zebra_day/imgs/legacy/zebra_day_home.png +0 -0
- zebra_day/imgs/legacy/zebra_day_manual_print.png +0 -0
- zebra_day/imgs/legacy/zebra_day_printer_fleet_json.png +0 -0
- zebra_day/imgs/legacy/zebra_day_quick_ex.png +0 -0
- zebra_day/imgs/legacy/zebra_day_zpl_template_IRLa.png +0 -0
- zebra_day/imgs/legacy/zebra_day_zpl_template_IRLb.png +0 -0
- zebra_day/imgs/ui_api_docs.png +0 -0
- zebra_day/imgs/ui_config.png +0 -0
- zebra_day/imgs/ui_dashboard.png +0 -0
- zebra_day/imgs/ui_print_request.png +0 -0
- zebra_day/imgs/ui_printers.png +0 -0
- zebra_day/imgs/ui_templates.png +0 -0
- zebra_day/logging_config.py +4 -9
- zebra_day/mkcert.py +157 -0
- zebra_day/paths.py +1 -2
- zebra_day/print_mgr.py +261 -185
- zebra_day/templates/modern/config.html +7 -0
- zebra_day/templates/modern/config_backups.html +59 -0
- zebra_day/templates/modern/config_editor.html +95 -0
- zebra_day/templates/modern/config_new.html +93 -0
- zebra_day/templates/modern/print_request.html +70 -8
- zebra_day/templates/modern/printer_detail.html +161 -34
- zebra_day/templates/modern/printers.html +17 -6
- zebra_day/templates/modern/template_editor.html +7 -4
- zebra_day/web/__init__.py +1 -1
- zebra_day/web/app.py +99 -17
- zebra_day/web/auth.py +17 -15
- zebra_day/web/middleware.py +8 -5
- zebra_day/web/routers/__init__.py +0 -1
- zebra_day/web/routers/api.py +330 -31
- zebra_day/web/routers/ui.py +174 -591
- zebra_day/zpl_renderer.py +45 -34
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/METADATA +144 -74
- zebra_day-2.1.4.dist-info/RECORD +240 -0
- zebra_day/bin/fetch_zebra_config.py +0 -15
- zebra_day/bin/generate_coord_grid_zpl.py +0 -50
- zebra_day/bin/print_zpl_from_file.py +0 -21
- zebra_day/bin/probe_new_label_dimensions.py +0 -75
- zebra_day/bin/scan_for_networed_zebra_printers.py +0 -23
- zebra_day/bin/scan_for_networed_zebra_printers_arp_scan.sh +0 -1
- zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +0 -30
- zebra_day/bin/zserve.py +0 -1062
- zebra_day/templates/base.html +0 -36
- zebra_day/templates/bpr.html +0 -72
- zebra_day/templates/build_new_config.html +0 -36
- zebra_day/templates/build_print_request.html +0 -32
- zebra_day/templates/chg_ui_style.html +0 -19
- zebra_day/templates/edit_template.html +0 -128
- zebra_day/templates/edit_zpl.html +0 -37
- zebra_day/templates/index.html +0 -82
- zebra_day/templates/legacy/base.html +0 -37
- zebra_day/templates/legacy/bpr.html +0 -72
- zebra_day/templates/legacy/build_new_config.html +0 -36
- zebra_day/templates/legacy/build_print_request.html +0 -32
- zebra_day/templates/legacy/chg_ui_style.html +0 -19
- zebra_day/templates/legacy/edit_template.html +0 -128
- zebra_day/templates/legacy/edit_zpl.html +0 -37
- zebra_day/templates/legacy/index.html +0 -82
- zebra_day/templates/legacy/list_prior_configs.html +0 -24
- zebra_day/templates/legacy/print_result.html +0 -30
- zebra_day/templates/legacy/printer_details.html +0 -25
- zebra_day/templates/legacy/printer_status.html +0 -70
- zebra_day/templates/legacy/save_result.html +0 -17
- zebra_day/templates/legacy/send_print_request.html +0 -34
- zebra_day/templates/legacy/simple_print.html +0 -94
- zebra_day/templates/legacy/view_pstation_json.html +0 -29
- zebra_day/templates/list_prior_configs.html +0 -24
- zebra_day/templates/print_result.html +0 -30
- zebra_day/templates/printer_details.html +0 -25
- zebra_day/templates/printer_status.html +0 -70
- zebra_day/templates/save_result.html +0 -17
- zebra_day/templates/send_print_request.html +0 -34
- zebra_day/templates/simple_print.html +0 -94
- zebra_day/templates/view_pstation_json.html +0 -29
- zebra_day-1.0.2.dist-info/RECORD +0 -179
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/WHEEL +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/entry_points.txt +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/licenses/LICENSE +0 -0
- {zebra_day-1.0.2.dist-info → zebra_day-2.1.4.dist-info}/top_level.txt +0 -0
|
@@ -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/__init__.py
CHANGED
zebra_day/web/app.py
CHANGED
|
@@ -3,20 +3,21 @@ FastAPI application factory for zebra_day.
|
|
|
3
3
|
|
|
4
4
|
This module provides the main FastAPI application for the zebra_day web interface.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import os
|
|
9
10
|
import subprocess
|
|
11
|
+
from importlib.resources import files
|
|
10
12
|
from pathlib import Path
|
|
11
|
-
from typing import Literal
|
|
13
|
+
from typing import Literal
|
|
12
14
|
|
|
13
|
-
from fastapi import FastAPI
|
|
15
|
+
from fastapi import FastAPI
|
|
14
16
|
from fastapi.staticfiles import StaticFiles
|
|
15
17
|
from fastapi.templating import Jinja2Templates
|
|
16
|
-
from importlib.resources import files
|
|
17
18
|
|
|
18
|
-
from zebra_day.logging_config import get_logger
|
|
19
19
|
from zebra_day import paths as xdg
|
|
20
|
+
from zebra_day.logging_config import get_logger
|
|
20
21
|
from zebra_day.web.middleware import RequestLoggingMiddleware, print_rate_limiter
|
|
21
22
|
|
|
22
23
|
_log = get_logger(__name__)
|
|
@@ -39,7 +40,7 @@ def create_app(
|
|
|
39
40
|
*,
|
|
40
41
|
debug: bool = False,
|
|
41
42
|
css_theme: str = "lsmc.css",
|
|
42
|
-
auth:
|
|
43
|
+
auth: Literal["none", "cognito"] | None = None,
|
|
43
44
|
) -> FastAPI:
|
|
44
45
|
"""
|
|
45
46
|
Create and configure the FastAPI application.
|
|
@@ -76,7 +77,7 @@ def create_app(
|
|
|
76
77
|
from zebra_day.web.auth import CognitoAuthMiddleware, setup_cognito_auth
|
|
77
78
|
|
|
78
79
|
cognito_auth = setup_cognito_auth(app)
|
|
79
|
-
app.add_middleware(CognitoAuthMiddleware, cognito_auth=cognito_auth)
|
|
80
|
+
app.add_middleware(CognitoAuthMiddleware, cognito_auth=cognito_auth) # type: ignore[arg-type]
|
|
80
81
|
app.state.cognito_auth = cognito_auth
|
|
81
82
|
app.state.auth_mode = "cognito"
|
|
82
83
|
_log.info("Cognito authentication middleware enabled")
|
|
@@ -96,9 +97,14 @@ def create_app(
|
|
|
96
97
|
app.mount("/static", StaticFiles(directory=str(_STATIC_PATH)), name="static")
|
|
97
98
|
|
|
98
99
|
# Also mount package directories that need to be served
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
# Package files directory (for templates, previews generated in-package)
|
|
101
|
+
pkg_files_dir = _PKG_PATH / "files"
|
|
102
|
+
pkg_files_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
app.mount("/files", StaticFiles(directory=str(pkg_files_dir)), name="files")
|
|
104
|
+
|
|
105
|
+
# XDG generated files directory (for PNG downloads from dl_png printer)
|
|
106
|
+
xdg_generated_dir = xdg.get_generated_files_dir()
|
|
107
|
+
app.mount("/generated", StaticFiles(directory=str(xdg_generated_dir)), name="generated")
|
|
102
108
|
|
|
103
109
|
etc_dir = _PKG_PATH / "etc"
|
|
104
110
|
if etc_dir.exists():
|
|
@@ -109,7 +115,7 @@ def create_app(
|
|
|
109
115
|
app.state.templates = templates
|
|
110
116
|
|
|
111
117
|
# Register routers
|
|
112
|
-
from zebra_day.web.routers import
|
|
118
|
+
from zebra_day.web.routers import api, ui
|
|
113
119
|
|
|
114
120
|
app.include_router(ui.router)
|
|
115
121
|
app.include_router(api.router, prefix="/api/v1", tags=["api"])
|
|
@@ -141,11 +147,30 @@ def create_app(
|
|
|
141
147
|
return app
|
|
142
148
|
|
|
143
149
|
|
|
150
|
+
def get_default_cert_paths() -> tuple[Path | None, Path | None]:
|
|
151
|
+
"""
|
|
152
|
+
Get default certificate paths from XDG config directory.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Tuple of (cert_path, key_path) or (None, None) if not found.
|
|
156
|
+
"""
|
|
157
|
+
config_dir = xdg.get_config_dir()
|
|
158
|
+
cert_dir = config_dir / "certs"
|
|
159
|
+
cert_file = cert_dir / "server.crt"
|
|
160
|
+
key_file = cert_dir / "server.key"
|
|
161
|
+
|
|
162
|
+
if cert_file.exists() and key_file.exists():
|
|
163
|
+
return cert_file, key_file
|
|
164
|
+
return None, None
|
|
165
|
+
|
|
166
|
+
|
|
144
167
|
def run_server(
|
|
145
168
|
host: str = "0.0.0.0",
|
|
146
169
|
port: int = 8118,
|
|
147
170
|
reload: bool = False,
|
|
148
171
|
auth: Literal["none", "cognito"] = "none",
|
|
172
|
+
ssl_certfile: str | None = None,
|
|
173
|
+
ssl_keyfile: str | None = None,
|
|
149
174
|
):
|
|
150
175
|
"""
|
|
151
176
|
Run the FastAPI server using uvicorn.
|
|
@@ -155,17 +180,74 @@ def run_server(
|
|
|
155
180
|
port: Port to listen on
|
|
156
181
|
reload: Enable auto-reload for development
|
|
157
182
|
auth: Authentication mode - "none" (public) or "cognito" (AWS Cognito)
|
|
183
|
+
ssl_certfile: Path to SSL certificate file (PEM format)
|
|
184
|
+
ssl_keyfile: Path to SSL private key file (PEM format)
|
|
185
|
+
|
|
186
|
+
If ssl_certfile and ssl_keyfile are not provided, the server will:
|
|
187
|
+
1. Check SSL_CERT_PATH and SSL_KEY_PATH environment variables
|
|
188
|
+
2. Check for certificates in ~/.config/zebra_day/certs/
|
|
189
|
+
3. Fall back to HTTP with a warning if no certificates are found
|
|
158
190
|
"""
|
|
159
191
|
import uvicorn
|
|
160
192
|
|
|
161
193
|
# Store auth mode in environment for factory function
|
|
162
194
|
os.environ["ZEBRA_DAY_AUTH_MODE"] = auth
|
|
163
195
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
# Resolve SSL certificate paths
|
|
197
|
+
cert_path = ssl_certfile
|
|
198
|
+
key_path = ssl_keyfile
|
|
199
|
+
|
|
200
|
+
# Check environment variables if not provided
|
|
201
|
+
if not cert_path:
|
|
202
|
+
cert_path = os.environ.get("SSL_CERT_PATH")
|
|
203
|
+
if not key_path:
|
|
204
|
+
key_path = os.environ.get("SSL_KEY_PATH")
|
|
205
|
+
|
|
206
|
+
# Check default XDG paths if still not found
|
|
207
|
+
if not cert_path or not key_path:
|
|
208
|
+
default_cert, default_key = get_default_cert_paths()
|
|
209
|
+
if default_cert and default_key:
|
|
210
|
+
cert_path = str(default_cert)
|
|
211
|
+
key_path = str(default_key)
|
|
212
|
+
|
|
213
|
+
# Validate certificate files exist
|
|
214
|
+
use_ssl = False
|
|
215
|
+
if cert_path and key_path:
|
|
216
|
+
cert_exists = Path(cert_path).exists()
|
|
217
|
+
key_exists = Path(key_path).exists()
|
|
218
|
+
if cert_exists and key_exists:
|
|
219
|
+
use_ssl = True
|
|
220
|
+
_log.info("HTTPS enabled with certificates:")
|
|
221
|
+
_log.info(" Certificate: %s", cert_path)
|
|
222
|
+
_log.info(" Private key: %s", key_path)
|
|
223
|
+
else:
|
|
224
|
+
if not cert_exists:
|
|
225
|
+
_log.warning("SSL certificate not found: %s", cert_path)
|
|
226
|
+
if not key_exists:
|
|
227
|
+
_log.warning("SSL private key not found: %s", key_path)
|
|
228
|
+
_log.warning("Falling back to HTTP (insecure)")
|
|
229
|
+
else:
|
|
230
|
+
_log.warning(
|
|
231
|
+
"No SSL certificates configured. Running in HTTP mode (insecure). "
|
|
232
|
+
"For HTTPS, run: mkcert -install && mkcert -cert-file ~/.config/zebra_day/certs/server.crt "
|
|
233
|
+
"-key-file ~/.config/zebra_day/certs/server.key localhost 127.0.0.1 ::1"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Build uvicorn config
|
|
237
|
+
uvicorn_kwargs: dict[str, str | int | bool | None] = {
|
|
238
|
+
"host": host,
|
|
239
|
+
"port": port,
|
|
240
|
+
"reload": reload,
|
|
241
|
+
"factory": True,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if use_ssl:
|
|
245
|
+
uvicorn_kwargs["ssl_certfile"] = cert_path
|
|
246
|
+
uvicorn_kwargs["ssl_keyfile"] = key_path
|
|
247
|
+
protocol = "https"
|
|
248
|
+
else:
|
|
249
|
+
protocol = "http"
|
|
250
|
+
|
|
251
|
+
_log.info("Starting server at %s://%s:%d", protocol, host, port)
|
|
171
252
|
|
|
253
|
+
uvicorn.run("zebra_day.web.app:create_app", **uvicorn_kwargs) # type: ignore[arg-type]
|
zebra_day/web/auth.py
CHANGED
|
@@ -5,10 +5,10 @@ Provides optional Cognito authentication support via the daylily-cognito library
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
10
|
|
|
11
|
-
from fastapi import
|
|
11
|
+
from fastapi import HTTPException, Request, status
|
|
12
12
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
13
13
|
from starlette.responses import Response
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
_log = get_logger(__name__)
|
|
21
21
|
|
|
22
22
|
# Endpoints that should never require authentication
|
|
23
|
-
PUBLIC_PATHS:
|
|
23
|
+
PUBLIC_PATHS: list[str] = [
|
|
24
24
|
"/healthz",
|
|
25
25
|
"/readyz",
|
|
26
26
|
"/docs",
|
|
@@ -30,7 +30,7 @@ PUBLIC_PATHS: List[str] = [
|
|
|
30
30
|
|
|
31
31
|
# Try to import daylily-cognito components
|
|
32
32
|
_COGNITO_AVAILABLE = False
|
|
33
|
-
_COGNITO_IMPORT_ERROR:
|
|
33
|
+
_COGNITO_IMPORT_ERROR: str | None = None
|
|
34
34
|
|
|
35
35
|
try:
|
|
36
36
|
from daylily_cognito import CognitoAuth, CognitoConfig, create_auth_dependency
|
|
@@ -38,9 +38,9 @@ try:
|
|
|
38
38
|
_COGNITO_AVAILABLE = True
|
|
39
39
|
except ImportError as e:
|
|
40
40
|
_COGNITO_IMPORT_ERROR = str(e)
|
|
41
|
-
CognitoAuth = None
|
|
42
|
-
CognitoConfig = None
|
|
43
|
-
create_auth_dependency = None
|
|
41
|
+
CognitoAuth = None
|
|
42
|
+
CognitoConfig = None
|
|
43
|
+
create_auth_dependency = None
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def is_cognito_available() -> bool:
|
|
@@ -48,7 +48,7 @@ def is_cognito_available() -> bool:
|
|
|
48
48
|
return _COGNITO_AVAILABLE
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def get_cognito_import_error() ->
|
|
51
|
+
def get_cognito_import_error() -> str | None:
|
|
52
52
|
"""Get the import error message if daylily-cognito is not available."""
|
|
53
53
|
return _COGNITO_IMPORT_ERROR
|
|
54
54
|
|
|
@@ -59,7 +59,7 @@ class CognitoAuthMiddleware(BaseHTTPMiddleware):
|
|
|
59
59
|
Exempts health check endpoints and other public paths.
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
|
-
def __init__(self, app:
|
|
62
|
+
def __init__(self, app: FastAPI, cognito_auth: Any) -> None:
|
|
63
63
|
super().__init__(app)
|
|
64
64
|
self.cognito_auth = cognito_auth
|
|
65
65
|
self.get_current_user = create_auth_dependency(cognito_auth, optional=False)
|
|
@@ -70,11 +70,13 @@ class CognitoAuthMiddleware(BaseHTTPMiddleware):
|
|
|
70
70
|
|
|
71
71
|
# Allow public endpoints without authentication
|
|
72
72
|
if any(path.startswith(public) for public in PUBLIC_PATHS):
|
|
73
|
-
|
|
73
|
+
response = await call_next(request)
|
|
74
|
+
return response # type: ignore[no-any-return]
|
|
74
75
|
|
|
75
76
|
# Allow static files without authentication
|
|
76
77
|
if path.startswith("/static") or path.startswith("/files") or path.startswith("/etc"):
|
|
77
|
-
|
|
78
|
+
response = await call_next(request)
|
|
79
|
+
return response # type: ignore[no-any-return]
|
|
78
80
|
|
|
79
81
|
# Check for Authorization header
|
|
80
82
|
auth_header = request.headers.get("Authorization")
|
|
@@ -116,10 +118,11 @@ class CognitoAuthMiddleware(BaseHTTPMiddleware):
|
|
|
116
118
|
headers={"WWW-Authenticate": "Bearer"},
|
|
117
119
|
)
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
response = await call_next(request)
|
|
122
|
+
return response # type: ignore[no-any-return]
|
|
120
123
|
|
|
121
124
|
|
|
122
|
-
def setup_cognito_auth(app:
|
|
125
|
+
def setup_cognito_auth(app: FastAPI) -> Any:
|
|
123
126
|
"""Configure Cognito authentication for the FastAPI app.
|
|
124
127
|
|
|
125
128
|
Reads configuration from environment variables:
|
|
@@ -169,4 +172,3 @@ def setup_cognito_auth(app: "FastAPI") -> Any:
|
|
|
169
172
|
)
|
|
170
173
|
|
|
171
174
|
return cognito_auth
|
|
172
|
-
|
zebra_day/web/middleware.py
CHANGED
|
@@ -3,12 +3,13 @@ Middleware for the zebra_day FastAPI application.
|
|
|
3
3
|
|
|
4
4
|
Provides request logging and rate limiting functionality.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import asyncio
|
|
9
10
|
import time
|
|
10
11
|
from collections import defaultdict
|
|
11
|
-
from
|
|
12
|
+
from collections.abc import Callable
|
|
12
13
|
|
|
13
14
|
from fastapi import Request, Response
|
|
14
15
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
@@ -33,7 +34,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
|
33
34
|
client_ip = request.client.host if request.client else "unknown"
|
|
34
35
|
method = request.method
|
|
35
36
|
path = request.url.path
|
|
36
|
-
|
|
37
|
+
str(request.query_params) if request.query_params else ""
|
|
37
38
|
|
|
38
39
|
# Extract relevant parameters for print operations
|
|
39
40
|
lab = request.query_params.get("lab", "")
|
|
@@ -86,7 +87,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
|
86
87
|
else:
|
|
87
88
|
_log.info("Request completed", extra=log_context)
|
|
88
89
|
|
|
89
|
-
return response
|
|
90
|
+
return response # type: ignore[no-any-return]
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
class PrintRateLimiter:
|
|
@@ -136,7 +137,10 @@ class PrintRateLimiter:
|
|
|
136
137
|
|
|
137
138
|
# Check rate limit
|
|
138
139
|
if len(self._request_times[client_ip]) >= self.max_requests:
|
|
139
|
-
return
|
|
140
|
+
return (
|
|
141
|
+
False,
|
|
142
|
+
f"Rate limit exceeded: {self.max_requests} requests per {self.window_seconds}s",
|
|
143
|
+
)
|
|
140
144
|
|
|
141
145
|
# Try to acquire semaphore (non-blocking check)
|
|
142
146
|
if self._semaphore.locked() and self._semaphore._value == 0:
|
|
@@ -156,4 +160,3 @@ class PrintRateLimiter:
|
|
|
156
160
|
|
|
157
161
|
# Global rate limiter instance
|
|
158
162
|
print_rate_limiter = PrintRateLimiter()
|
|
159
|
-
|