From d91a396c0035a3d74334e237dc694a4a26b669f5 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Thu, 7 May 2026 06:41:42 +0300 Subject: [PATCH] feat(webui): add HTML and panel view for worktime reports --- ansible/deploy_aw_server.yml | 2 + .../provision_proxmox_ct_and_deploy_aw.yml | 1 + ...vision_proxmox_ct_matrix_and_deploy_aw.yml | 1 + aw-server/apply_webui_ru_patch.sh | 69 ++++++++-- aw-server/aw-worktime-api.py | 130 ++++++++++++++++++ aw-server/aw-worktime-panel.js | 56 ++++++++ .../ansible/deploy_aw_server.yml | 2 + .../provision_proxmox_ct_and_deploy_aw.yml | 1 + ...vision_proxmox_ct_matrix_and_deploy_aw.yml | 1 + .../aw-server/apply_webui_ru_patch.sh | 66 +++++++-- .../aw-server/aw-worktime-api.py | 130 ++++++++++++++++++ .../aw-server/aw-worktime-panel.js | 56 ++++++++ 12 files changed, 494 insertions(+), 21 deletions(-) create mode 100644 aw-server/aw-worktime-panel.js create mode 100644 install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index 2eb2888..9c775b7 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -254,6 +254,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh @@ -270,6 +271,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" } - name: Скопировать apply_webui_ru_patch.sh скрипт diff --git a/ansible/provision_proxmox_ct_and_deploy_aw.yml b/ansible/provision_proxmox_ct_and_deploy_aw.yml index 206a37c..40c6382 100644 --- a/ansible/provision_proxmox_ct_and_deploy_aw.yml +++ b/ansible/provision_proxmox_ct_and_deploy_aw.yml @@ -11,6 +11,7 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js - aw-sw-cleanup.js diff --git a/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml index ec1a4f3..5452935 100644 --- a/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml +++ b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml @@ -11,6 +11,7 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js - aw-sw-cleanup.js diff --git a/aw-server/apply_webui_ru_patch.sh b/aw-server/apply_webui_ru_patch.sh index cb92a51..32e53fa 100755 --- a/aw-server/apply_webui_ru_patch.sh +++ b/aw-server/apply_webui_ru_patch.sh @@ -13,12 +13,14 @@ WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-${AW_WEBUI_DIR:-/opt/activitywatch/webui-ru}}" REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://10.10.10.13:5610}" PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js" SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js" +WORKTIME_PANEL_SRC="/root/bootstrap/aw-worktime-panel.js" HOST_GROUPS_SRC="/root/bootstrap/aw-host-groups.json" INDEX_HTML="$WEBUI_DIR/index.html" SERVICE_WORKER="$WEBUI_DIR/service-worker.js" TS=$(date +%Y%m%d%H%M%S) PATCH_TARGET="$WEBUI_DIR/js/ru-patch-v5.js" SW_TARGET="$WEBUI_DIR/js/sw-cleanup.js" +WORKTIME_PANEL_TARGET="$WEBUI_DIR/js/aw-worktime-panel.js" HOST_GROUPS_TARGET="$WEBUI_DIR/js/aw-host-groups.json" TRENDS_NEEDLE='this.activityStore.query_category_time_by_period(r)' TRENDS_REPLACEMENT='this.activityStore.ensure_loaded(r)' @@ -29,33 +31,76 @@ CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){retu [[ -f "$PATCH_JS_SRC" ]] || { echo "missing $PATCH_JS_SRC" >&2; exit 1; } [[ -f "$SW_CLEANUP_SRC" ]] || { echo "missing $SW_CLEANUP_SRC" >&2; exit 1; } +[[ -f "$WORKTIME_PANEL_SRC" ]] || { echo "missing $WORKTIME_PANEL_SRC" >&2; exit 1; } [[ -f "$HOST_GROUPS_SRC" ]] || { echo "missing $HOST_GROUPS_SRC" >&2; exit 1; } [[ -f "$INDEX_HTML" ]] || { echo "missing $INDEX_HTML" >&2; exit 1; } install -d "$WEBUI_DIR/js" install -m 0644 "$PATCH_JS_SRC" "$PATCH_TARGET" install -m 0644 "$SW_CLEANUP_SRC" "$SW_TARGET" +install -m 0644 "$WORKTIME_PANEL_SRC" "$WORKTIME_PANEL_TARGET" install -m 0644 "$HOST_GROUPS_SRC" "$HOST_GROUPS_TARGET" cp "$INDEX_HTML" "$INDEX_HTML.bak.$TS" patch_hash="$(sha1sum "$PATCH_TARGET" | awk '{print substr($1,1,12)}')" sw_hash="$(sha1sum "$SW_TARGET" | awk '{print substr($1,1,12)}')" +worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,1,12)}')" -sed -i '/ru-patch-v5.js/d;/sw-cleanup.js/d;/aw-ru-patch.js/d;/aw-sw-cleanup.js/d' "$INDEX_HTML" -sed -i "s###" "$INDEX_HTML" -sed -i "s###" "$INDEX_HTML" +python3 - "$WORKTIME_PANEL_TARGET" "$REPORT_BASE" <<'PY' +from pathlib import Path +import sys -# Replace old relative report links if they exist. -sed -i "s#href=\"/today.csv\"#href=\"$REPORT_BASE/reports/worktime/today?format=csv\"#g" "$INDEX_HTML" -sed -i "s#href=\"/today.json\"#href=\"$REPORT_BASE/reports/worktime/today\"#g" "$INDEX_HTML" +path = Path(sys.argv[1]) +report_base = sys.argv[2] +text = path.read_text() +text = text.replace("__AW_WORKTIME_REPORT_BASE__", report_base) +path.write_text(text) +PY -# Ensure report links block exists and points to worktime API on :5610. -if ! grep -q 'id="aw-report-links"' "$INDEX_HTML"; then - sed -i "s##
RDP report: CSV | JSON
#g" "$INDEX_HTML" -fi +python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" <<'PY' +from pathlib import Path +import re +import sys -# Normalize existing report links block to current REPORT_BASE. -sed -i "s#
CSV | JSON
#
RDP report: CSV | JSON
#g" "$INDEX_HTML" +path = Path(sys.argv[1]) +sw_hash = sys.argv[2] +patch_hash = sys.argv[3] +panel_hash = sys.argv[4] +report_base = sys.argv[5] +content = path.read_text() + +content = re.sub( + r']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js|aw-worktime-panel\.js)[^>]*>', + '', + content, +) +content = re.sub(r"; frame-src 'self' [^\";>]*", "", content) +content = content.replace( + "script-src 'self' 'unsafe-eval'", + f"script-src 'self' 'unsafe-eval'; frame-src 'self' {report_base}", + 1, +) +content = content.replace( + "", + f'', + 1, +) +content = content.replace( + "", + ( + f'' + f'' + ), + 1, +) +if 'id="aw-report-links"' not in content: + content = content.replace( + "", + '', + 1, + ) +path.write_text(content) +PY cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER" trends_chunk="$(grep -Rsl "$TRENDS_NEEDLE" "$WEBUI_DIR/js"/*.js 2>/dev/null | head -n 1 || true)" diff --git a/aw-server/aw-worktime-api.py b/aw-server/aw-worktime-api.py index 29f7d11..6f6611d 100644 --- a/aw-server/aw-worktime-api.py +++ b/aw-server/aw-worktime-api.py @@ -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( + "" + f"{row['user']}" + f"{row['active_hhmm']}" + f"{row['active_seconds']}" + f"{row['first_activity']}" + f"{row['last_activity']}" + f"{row['idle_seconds']}" + f"{row['sessions_count']}" + "" + ) + if not trs: + trs.append('No data for today yet.') + return f""" + + + + + AW-rus Worktime + + + +
+
+

RDP Worktime Report

+
Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}
+ +
+
+ + + + + + + + + + + + + + {''.join(trs)} + +
UserActiveActive secFirst activityLast activityIdle secSamples
+
+
+ +""" + + 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), diff --git a/aw-server/aw-worktime-panel.js b/aw-server/aw-worktime-panel.js new file mode 100644 index 0000000..c38b29e --- /dev/null +++ b/aw-server/aw-worktime-panel.js @@ -0,0 +1,56 @@ +(function () { + var reportBase = "__AW_WORKTIME_REPORT_BASE__"; + var reportUrl = reportBase + "/reports/worktime/today?format=html"; + var existing = document.getElementById("aw-report-links"); + if (!existing) return; + + existing.innerHTML = + 'RDP report: ' + + 'HTML | ' + + 'CSV | ' + + 'JSON | ' + + 'Panel'; + + var panel = document.createElement("div"); + panel.id = "aw-report-panel"; + panel.style.cssText = [ + "position:fixed", + "top:16px", + "right:16px", + "width:min(980px,calc(100vw - 32px))", + "height:min(760px,calc(100vh - 32px))", + "background:#fff", + "border:1px solid rgba(15,23,42,.15)", + "border-radius:12px", + "box-shadow:0 24px 80px rgba(15,23,42,.28)", + "overflow:hidden", + "z-index:100000", + "display:none" + ].join(";"); + + panel.innerHTML = + '
' + + '
RDP Worktime Report
' + + '
' + + 'Open' + + 'Close' + + "
" + + ''; + + document.body.appendChild(panel); + + function openPanel(ev) { + if (ev) ev.preventDefault(); + panel.style.display = "block"; + } + + function closePanel(ev) { + if (ev) ev.preventDefault(); + panel.style.display = "none"; + } + + var toggle = document.getElementById("aw-report-toggle"); + if (toggle) toggle.addEventListener("click", openPanel); + var close = panel.querySelector("#aw-report-close"); + if (close) close.addEventListener("click", closePanel); +})(); diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml index 040ec77..b136df7 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml @@ -194,6 +194,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh @@ -210,6 +211,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" } - name: Скопировать apply_webui_ru_patch.sh скрипт diff --git a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml index 206a37c..40c6382 100644 --- a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml +++ b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml @@ -11,6 +11,7 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js - aw-sw-cleanup.js diff --git a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml index ec1a4f3..5452935 100644 --- a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml +++ b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml @@ -11,6 +11,7 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js - aw-sw-cleanup.js diff --git a/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh b/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh index c75ea54..32e53fa 100755 --- a/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh +++ b/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh @@ -13,12 +13,14 @@ WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-${AW_WEBUI_DIR:-/opt/activitywatch/webui-ru}}" REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://10.10.10.13:5610}" PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js" SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js" +WORKTIME_PANEL_SRC="/root/bootstrap/aw-worktime-panel.js" HOST_GROUPS_SRC="/root/bootstrap/aw-host-groups.json" INDEX_HTML="$WEBUI_DIR/index.html" SERVICE_WORKER="$WEBUI_DIR/service-worker.js" TS=$(date +%Y%m%d%H%M%S) PATCH_TARGET="$WEBUI_DIR/js/ru-patch-v5.js" SW_TARGET="$WEBUI_DIR/js/sw-cleanup.js" +WORKTIME_PANEL_TARGET="$WEBUI_DIR/js/aw-worktime-panel.js" HOST_GROUPS_TARGET="$WEBUI_DIR/js/aw-host-groups.json" TRENDS_NEEDLE='this.activityStore.query_category_time_by_period(r)' TRENDS_REPLACEMENT='this.activityStore.ensure_loaded(r)' @@ -29,30 +31,76 @@ CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){retu [[ -f "$PATCH_JS_SRC" ]] || { echo "missing $PATCH_JS_SRC" >&2; exit 1; } [[ -f "$SW_CLEANUP_SRC" ]] || { echo "missing $SW_CLEANUP_SRC" >&2; exit 1; } +[[ -f "$WORKTIME_PANEL_SRC" ]] || { echo "missing $WORKTIME_PANEL_SRC" >&2; exit 1; } [[ -f "$HOST_GROUPS_SRC" ]] || { echo "missing $HOST_GROUPS_SRC" >&2; exit 1; } [[ -f "$INDEX_HTML" ]] || { echo "missing $INDEX_HTML" >&2; exit 1; } install -d "$WEBUI_DIR/js" install -m 0644 "$PATCH_JS_SRC" "$PATCH_TARGET" install -m 0644 "$SW_CLEANUP_SRC" "$SW_TARGET" +install -m 0644 "$WORKTIME_PANEL_SRC" "$WORKTIME_PANEL_TARGET" install -m 0644 "$HOST_GROUPS_SRC" "$HOST_GROUPS_TARGET" cp "$INDEX_HTML" "$INDEX_HTML.bak.$TS" patch_hash="$(sha1sum "$PATCH_TARGET" | awk '{print substr($1,1,12)}')" sw_hash="$(sha1sum "$SW_TARGET" | awk '{print substr($1,1,12)}')" +worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,1,12)}')" -sed -i '/ru-patch-v5.js/d;/sw-cleanup.js/d;/aw-ru-patch.js/d;/aw-sw-cleanup.js/d' "$INDEX_HTML" -sed -i "s###" "$INDEX_HTML" -sed -i "s###" "$INDEX_HTML" +python3 - "$WORKTIME_PANEL_TARGET" "$REPORT_BASE" <<'PY' +from pathlib import Path +import sys -sed -i "s#href=\"/today.csv\"#href=\"$REPORT_BASE/reports/worktime/today?format=csv\"#g" "$INDEX_HTML" -sed -i "s#href=\"/today.json\"#href=\"$REPORT_BASE/reports/worktime/today\"#g" "$INDEX_HTML" +path = Path(sys.argv[1]) +report_base = sys.argv[2] +text = path.read_text() +text = text.replace("__AW_WORKTIME_REPORT_BASE__", report_base) +path.write_text(text) +PY -if ! grep -q 'id="aw-report-links"' "$INDEX_HTML"; then - sed -i "s##
RDP report: CSV | JSON
#g" "$INDEX_HTML" -fi +python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" <<'PY' +from pathlib import Path +import re +import sys -sed -i "s#
CSV | JSON
#
RDP report: CSV | JSON
#g" "$INDEX_HTML" +path = Path(sys.argv[1]) +sw_hash = sys.argv[2] +patch_hash = sys.argv[3] +panel_hash = sys.argv[4] +report_base = sys.argv[5] +content = path.read_text() + +content = re.sub( + r']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js|aw-worktime-panel\.js)[^>]*>', + '', + content, +) +content = re.sub(r"; frame-src 'self' [^\";>]*", "", content) +content = content.replace( + "script-src 'self' 'unsafe-eval'", + f"script-src 'self' 'unsafe-eval'; frame-src 'self' {report_base}", + 1, +) +content = content.replace( + "", + f'', + 1, +) +content = content.replace( + "", + ( + f'' + f'' + ), + 1, +) +if 'id="aw-report-links"' not in content: + content = content.replace( + "", + '', + 1, + ) +path.write_text(content) +PY cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER" trends_chunk="$(grep -Rsl "$TRENDS_NEEDLE" "$WEBUI_DIR/js"/*.js 2>/dev/null | head -n 1 || true)" diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py index 29f7d11..6f6611d 100644 --- a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py +++ b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py @@ -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( + "" + f"{row['user']}" + f"{row['active_hhmm']}" + f"{row['active_seconds']}" + f"{row['first_activity']}" + f"{row['last_activity']}" + f"{row['idle_seconds']}" + f"{row['sessions_count']}" + "" + ) + if not trs: + trs.append('No data for today yet.') + return f""" + + + + + AW-rus Worktime + + + +
+
+

RDP Worktime Report

+
Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}
+ +
+
+ + + + + + + + + + + + + + {''.join(trs)} + +
UserActiveActive secFirst activityLast activityIdle secSamples
+
+
+ +""" + + 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), diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js new file mode 100644 index 0000000..c38b29e --- /dev/null +++ b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js @@ -0,0 +1,56 @@ +(function () { + var reportBase = "__AW_WORKTIME_REPORT_BASE__"; + var reportUrl = reportBase + "/reports/worktime/today?format=html"; + var existing = document.getElementById("aw-report-links"); + if (!existing) return; + + existing.innerHTML = + 'RDP report: ' + + 'HTML | ' + + 'CSV | ' + + 'JSON | ' + + 'Panel'; + + var panel = document.createElement("div"); + panel.id = "aw-report-panel"; + panel.style.cssText = [ + "position:fixed", + "top:16px", + "right:16px", + "width:min(980px,calc(100vw - 32px))", + "height:min(760px,calc(100vh - 32px))", + "background:#fff", + "border:1px solid rgba(15,23,42,.15)", + "border-radius:12px", + "box-shadow:0 24px 80px rgba(15,23,42,.28)", + "overflow:hidden", + "z-index:100000", + "display:none" + ].join(";"); + + panel.innerHTML = + '
' + + '
RDP Worktime Report
' + + '
' + + 'Open' + + 'Close' + + "
" + + ''; + + document.body.appendChild(panel); + + function openPanel(ev) { + if (ev) ev.preventDefault(); + panel.style.display = "block"; + } + + function closePanel(ev) { + if (ev) ev.preventDefault(); + panel.style.display = "none"; + } + + var toggle = document.getElementById("aw-report-toggle"); + if (toggle) toggle.addEventListener("click", openPanel); + var close = panel.querySelector("#aw-report-close"); + if (close) close.addEventListener("click", closePanel); +})();