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#
+
+
+ RDP Worktime Report
+ Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}
+
+
+
+
+
+
+ | User |
+ Active |
+ Active sec |
+ First activity |
+ Last activity |
+ Idle sec |
+ Samples |
+
+
+
+ {''.join(trs)}
+
+
+
+
+##" "$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##
#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#
#
#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'',
+ '',
+ 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(
+ "",
+ '
RDP report: loading...
',
+ 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
+
+
+
+