Upload 67 files
Browse files- app/__pycache__/main.cpython-313.pyc +0 -0
- app/__pycache__/web.cpython-313.pyc +0 -0
- app/main.py +5 -1
- app/static/style.css +64 -0
- app/templates/admin_login.html +2 -1
- app/templates/base.html +4 -2
- app/templates/login.html +2 -1
- app/web.py +14 -0
app/__pycache__/main.cpython-313.pyc
CHANGED
|
Binary files a/app/__pycache__/main.cpython-313.pyc and b/app/__pycache__/main.cpython-313.pyc differ
|
|
|
app/__pycache__/web.cpython-313.pyc
CHANGED
|
Binary files a/app/__pycache__/web.cpython-313.pyc and b/app/__pycache__/web.cpython-313.pyc differ
|
|
|
app/main.py
CHANGED
|
@@ -34,7 +34,10 @@ def on_startup() -> None:
|
|
| 34 |
@app.middleware("http")
|
| 35 |
async def track_presence(request: Request, call_next):
|
| 36 |
response = await call_next(request)
|
| 37 |
-
if request.url.path.startswith("/static")
|
|
|
|
|
|
|
|
|
|
| 38 |
return response
|
| 39 |
if request.url.path.startswith("/api"):
|
| 40 |
return response
|
|
@@ -120,3 +123,4 @@ templates.env.filters["datetime_local"] = format_datetime
|
|
| 120 |
templates.env.filters["duration_human"] = format_timedelta
|
| 121 |
|
| 122 |
|
|
|
|
|
|
| 34 |
@app.middleware("http")
|
| 35 |
async def track_presence(request: Request, call_next):
|
| 36 |
response = await call_next(request)
|
| 37 |
+
if request.url.path.startswith("/static"):
|
| 38 |
+
response.headers["Cache-Control"] = "no-store"
|
| 39 |
+
return response
|
| 40 |
+
if request.url.path.startswith("/media"):
|
| 41 |
return response
|
| 42 |
if request.url.path.startswith("/api"):
|
| 43 |
return response
|
|
|
|
| 123 |
templates.env.filters["duration_human"] = format_timedelta
|
| 124 |
|
| 125 |
|
| 126 |
+
|
app/static/style.css
CHANGED
|
@@ -1287,3 +1287,67 @@ button {
|
|
| 1287 |
width: 100%;
|
| 1288 |
}
|
| 1289 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1287 |
width: 100%;
|
| 1288 |
}
|
| 1289 |
}
|
| 1290 |
+
|
| 1291 |
+
/* Login layout restore */
|
| 1292 |
+
.login-body {
|
| 1293 |
+
min-height: 100vh;
|
| 1294 |
+
}
|
| 1295 |
+
|
| 1296 |
+
.login-page {
|
| 1297 |
+
width: min(1200px, calc(100% - 32px));
|
| 1298 |
+
min-height: 100vh;
|
| 1299 |
+
margin: 0 auto;
|
| 1300 |
+
padding: 28px 0 44px;
|
| 1301 |
+
display: grid;
|
| 1302 |
+
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 0.95fr);
|
| 1303 |
+
gap: 24px;
|
| 1304 |
+
align-items: stretch;
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
+
.compact-login-page {
|
| 1308 |
+
grid-template-columns: minmax(320px, 0.95fr) minmax(320px, 0.85fr);
|
| 1309 |
+
max-width: 1040px;
|
| 1310 |
+
}
|
| 1311 |
+
|
| 1312 |
+
.login-copy,
|
| 1313 |
+
.login-panel {
|
| 1314 |
+
padding: 30px;
|
| 1315 |
+
}
|
| 1316 |
+
|
| 1317 |
+
.login-copy {
|
| 1318 |
+
display: grid;
|
| 1319 |
+
gap: 18px;
|
| 1320 |
+
align-content: center;
|
| 1321 |
+
}
|
| 1322 |
+
|
| 1323 |
+
.login-panel {
|
| 1324 |
+
display: grid;
|
| 1325 |
+
gap: 16px;
|
| 1326 |
+
align-content: start;
|
| 1327 |
+
}
|
| 1328 |
+
|
| 1329 |
+
.admin-panel {
|
| 1330 |
+
align-content: center;
|
| 1331 |
+
}
|
| 1332 |
+
|
| 1333 |
+
.activity-actions {
|
| 1334 |
+
display: flex;
|
| 1335 |
+
flex-wrap: wrap;
|
| 1336 |
+
align-items: center;
|
| 1337 |
+
justify-content: space-between;
|
| 1338 |
+
gap: 12px;
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
@media (max-width: 980px) {
|
| 1342 |
+
.login-page,
|
| 1343 |
+
.compact-login-page {
|
| 1344 |
+
width: min(100% - 24px, 1200px);
|
| 1345 |
+
padding: 20px 0 36px;
|
| 1346 |
+
grid-template-columns: 1fr;
|
| 1347 |
+
}
|
| 1348 |
+
|
| 1349 |
+
.login-copy,
|
| 1350 |
+
.login-panel {
|
| 1351 |
+
padding: 24px;
|
| 1352 |
+
}
|
| 1353 |
+
}
|
app/templates/admin_login.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>管理员登录 · {{ app_name }}</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
</head>
|
| 9 |
<body class="login-body admin-login-body">
|
| 10 |
<div class="page-backdrop"></div>
|
|
@@ -37,3 +37,4 @@
|
|
| 37 |
</main>
|
| 38 |
</body>
|
| 39 |
</html>
|
|
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>管理员登录 · {{ app_name }}</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css?v={{ static_asset_version('style.css') }}" />
|
| 8 |
</head>
|
| 9 |
<body class="login-body admin-login-body">
|
| 10 |
<div class="page-backdrop"></div>
|
|
|
|
| 37 |
</main>
|
| 38 |
</body>
|
| 39 |
</html>
|
| 40 |
+
|
app/templates/base.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>{{ page_title or app_name }} · {{ app_name }}</title>
|
| 7 |
-
<link rel="icon" href="/static/favicon.ico" sizes="any" />
|
| 8 |
-
<link rel="stylesheet" href="/static/style.css" />
|
| 9 |
</head>
|
| 10 |
<body class="{% if admin %}admin-mode{% else %}user-mode{% endif %}">
|
| 11 |
<div class="page-backdrop"></div>
|
|
@@ -122,3 +122,5 @@
|
|
| 122 |
|
| 123 |
|
| 124 |
|
|
|
|
|
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>{{ page_title or app_name }} · {{ app_name }}</title>
|
| 7 |
+
<link rel="icon" href="/static/favicon.ico?v={{ static_asset_version('favicon.ico') }}" sizes="any" />
|
| 8 |
+
<link rel="stylesheet" href="/static/style.css?v={{ static_asset_version('style.css') }}" />
|
| 9 |
</head>
|
| 10 |
<body class="{% if admin %}admin-mode{% else %}user-mode{% endif %}">
|
| 11 |
<div class="page-backdrop"></div>
|
|
|
|
| 122 |
|
| 123 |
|
| 124 |
|
| 125 |
+
|
| 126 |
+
|
app/templates/login.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>用户登录 · {{ app_name }}</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
</head>
|
| 9 |
<body class="login-body">
|
| 10 |
<div class="page-backdrop"></div>
|
|
@@ -42,3 +42,4 @@
|
|
| 42 |
</main>
|
| 43 |
</body>
|
| 44 |
</html>
|
|
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>用户登录 · {{ app_name }}</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css?v={{ static_asset_version('style.css') }}" />
|
| 8 |
</head>
|
| 9 |
<body class="login-body">
|
| 10 |
<div class="page-backdrop"></div>
|
|
|
|
| 42 |
</main>
|
| 43 |
</body>
|
| 44 |
</html>
|
| 45 |
+
|
app/web.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from datetime import datetime
|
|
|
|
| 4 |
from zoneinfo import ZoneInfo
|
| 5 |
|
| 6 |
from fastapi import Request
|
|
@@ -11,6 +12,17 @@ from app.config import ROOT_DIR, settings
|
|
| 11 |
|
| 12 |
|
| 13 |
templates = Jinja2Templates(directory=str(ROOT_DIR / "app" / "templates"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def local_now() -> datetime:
|
|
@@ -40,3 +52,5 @@ def render(request: Request, template_name: str, context: dict, status_code: int
|
|
| 40 |
|
| 41 |
def redirect(url: str, status_code: int = 303) -> RedirectResponse:
|
| 42 |
return RedirectResponse(url=url, status_code=status_code)
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from datetime import datetime
|
| 4 |
+
from pathlib import Path
|
| 5 |
from zoneinfo import ZoneInfo
|
| 6 |
|
| 7 |
from fastapi import Request
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
templates = Jinja2Templates(directory=str(ROOT_DIR / "app" / "templates"))
|
| 15 |
+
STATIC_DIR = ROOT_DIR / "app" / "static"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def static_asset_version(asset_name: str) -> int:
|
| 19 |
+
asset_path = STATIC_DIR / asset_name
|
| 20 |
+
if not asset_path.exists():
|
| 21 |
+
return 1
|
| 22 |
+
return int(asset_path.stat().st_mtime)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
templates.env.globals["static_asset_version"] = static_asset_version
|
| 26 |
|
| 27 |
|
| 28 |
def local_now() -> datetime:
|
|
|
|
| 52 |
|
| 53 |
def redirect(url: str, status_code: int = 303) -> RedirectResponse:
|
| 54 |
return RedirectResponse(url=url, status_code=status_code)
|
| 55 |
+
|
| 56 |
+
|