feat(webui): add HTML and panel view for worktime reports

This commit is contained in:
igor04091968
2026-05-07 06:41:42 +03:00
parent 8a91734a27
commit d91a396c00
12 changed files with 494 additions and 21 deletions
+130
View File
@@ -67,6 +67,126 @@ def report_today():
return rows
def render_html(rows):
generated = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
date_local = datetime.now(REPORT_TZ).strftime("%Y-%m-%d")
trs = []
for row in rows:
trs.append(
"<tr>"
f"<td>{row['user']}</td>"
f"<td>{row['active_hhmm']}</td>"
f"<td>{row['active_seconds']}</td>"
f"<td>{row['first_activity']}</td>"
f"<td>{row['last_activity']}</td>"
f"<td>{row['idle_seconds']}</td>"
f"<td>{row['sessions_count']}</td>"
"</tr>"
)
if not trs:
trs.append('<tr><td colspan="7">No data for today yet.</td></tr>')
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AW-rus Worktime</title>
<style>
:root {{
color-scheme: light;
--bg: #f4f7fb;
--card: #ffffff;
--line: #dbe3ee;
--text: #0f172a;
--muted: #475569;
--accent: #0f766e;
--accent-2: #1d4ed8;
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
font: 14px/1.45 "Segoe UI", "Noto Sans", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(29,78,216,.08), transparent 28%),
radial-gradient(circle at top right, rgba(15,118,110,.10), transparent 24%),
var(--bg);
}}
.wrap {{ max-width: 1180px; margin: 0 auto; padding: 24px; }}
.hero {{
background: linear-gradient(135deg, #0f172a, #1e293b 58%, #0f766e);
color: #fff;
border-radius: 18px;
padding: 20px 22px;
box-shadow: 0 22px 60px rgba(15,23,42,.22);
}}
.hero h1 {{ margin: 0 0 8px; font-size: 28px; }}
.meta {{ color: rgba(255,255,255,.84); }}
.actions {{ margin-top: 14px; display: flex; gap: 10px; flex-wrap: wrap; }}
.actions a {{
text-decoration: none;
color: #fff;
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.18);
padding: 8px 12px;
border-radius: 999px;
}}
.card {{
margin-top: 18px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(15,23,42,.08);
}}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; }}
th {{ background: #eef4fb; color: var(--muted); font-weight: 600; position: sticky; top: 0; }}
tr:nth-child(even) td {{ background: rgba(148,163,184,.06); }}
.num {{ font-variant-numeric: tabular-nums; }}
.good {{ color: var(--accent); font-weight: 700; }}
.muted {{ color: var(--muted); }}
@media (max-width: 900px) {{
.wrap {{ padding: 14px; }}
.hero h1 {{ font-size: 22px; }}
.card {{ overflow-x: auto; }}
table {{ min-width: 820px; }}
}}
</style>
</head>
<body>
<div class="wrap">
<section class="hero">
<h1>RDP Worktime Report</h1>
<div class="meta">Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}</div>
<div class="actions">
<a href="/reports/worktime/today?format=csv">Download CSV</a>
<a href="/reports/worktime/today">View JSON</a>
</div>
</section>
<section class="card">
<table>
<thead>
<tr>
<th>User</th>
<th>Active</th>
<th>Active sec</th>
<th>First activity</th>
<th>Last activity</th>
<th>Idle sec</th>
<th>Samples</th>
</tr>
</thead>
<tbody>
{''.join(trs)}
</tbody>
</table>
</section>
</div>
</body>
</html>"""
class H(BaseHTTPRequestHandler):
def do_GET(self):
if not self.path.startswith("/reports/worktime/today"):
@@ -76,6 +196,8 @@ class H(BaseHTTPRequestHandler):
fmt = "json"
if "format=csv" in self.path:
fmt = "csv"
elif "format=html" in self.path:
fmt = "html"
rows = report_today()
if fmt == "csv":
out = io.StringIO()
@@ -100,6 +222,14 @@ class H(BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(data)
return
if fmt == "html":
data = render_html(rows).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
obj = {
"generated_at_utc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"report_timezone": str(REPORT_TZ),