chore: merge main into PR #15
This commit is contained in:
@@ -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 скрипт
|
||||
@@ -233,9 +235,37 @@
|
||||
AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
|
||||
AW_SERVER_USER={{ aw_server_user }}
|
||||
AW_SERVER_GROUP={{ aw_server_group }}
|
||||
AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
|
||||
AW_WORKTIME_TZ={{ aw_worktime_timezone }}
|
||||
XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
|
||||
XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
|
||||
|
||||
- name: Установить скрипт AW worktime API
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py"
|
||||
dest: /usr/local/bin/aw-worktime-api.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить systemd unit AW worktime API
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service"
|
||||
dest: /etc/systemd/system/aw-worktime-api.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Перезагрузить systemd после установки AW worktime API
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Включить и перезапустить AW worktime API
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-api.service
|
||||
enabled: true
|
||||
state: restarted
|
||||
|
||||
- name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
|
||||
ansible.builtin.command:
|
||||
cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh"
|
||||
|
||||
@@ -7,6 +7,8 @@ aw_server_data_dir: "/var/lib/activitywatch"
|
||||
aw_server_log_dir: "/var/log/activitywatch"
|
||||
aw_server_user: "activitywatch"
|
||||
aw_server_group: "activitywatch"
|
||||
aw_worktime_report_base: "http://10.10.10.13:5610"
|
||||
aw_worktime_timezone: "Europe/Moscow"
|
||||
|
||||
aw_repo_root: "{{ playbook_dir | dirname }}"
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
- install_aw_server.sh
|
||||
- apply_webui_ru_patch.sh
|
||||
- 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
|
||||
|
||||
+3
@@ -9,6 +9,9 @@
|
||||
- install_aw_server.sh
|
||||
- apply_webui_ru_patch.sh
|
||||
- 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
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
|
||||
AW_SERVER_USER={{ aw_server_user }}
|
||||
AW_SERVER_GROUP={{ aw_server_group }}
|
||||
AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
|
||||
AW_WORKTIME_TZ={{ aw_worktime_timezone }}
|
||||
no_log: true
|
||||
|
||||
- name: Передать AW server env внутрь CT
|
||||
|
||||
@@ -10,14 +10,17 @@ fi
|
||||
source "$ENV_FILE"
|
||||
|
||||
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)'
|
||||
@@ -28,21 +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#</head>#<script src=\"/js/sw-cleanup.js?v=$sw_hash\"></script></head>#" "$INDEX_HTML"
|
||||
sed -i "s#</body>#<script defer=\"defer\" src=\"/js/ru-patch-v5.js?v=$patch_hash\"></script></body>#" "$INDEX_HTML"
|
||||
python3 - "$WORKTIME_PANEL_TARGET" "$REPORT_BASE" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
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
|
||||
|
||||
python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
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'<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js|aw-worktime-panel\.js)[^>]*></script>',
|
||||
'',
|
||||
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(
|
||||
"</head>",
|
||||
f'<script src="/js/sw-cleanup.js?v={sw_hash}"></script></head>',
|
||||
1,
|
||||
)
|
||||
content = content.replace(
|
||||
"</body>",
|
||||
(
|
||||
f'<script defer="defer" src="/js/ru-patch-v5.js?v={patch_hash}"></script>'
|
||||
f'<script defer="defer" src="/js/aw-worktime-panel.js?v={panel_hash}"></script></body>'
|
||||
),
|
||||
1,
|
||||
)
|
||||
if 'id="aw-report-links"' not in content:
|
||||
content = content.replace(
|
||||
"</body>",
|
||||
'<div id="aw-report-links" style="position:fixed;right:12px;bottom:12px;z-index:99999;background:#111;color:#fff;padding:8px 10px;border-radius:8px;font:12px/1.4 sans-serif;opacity:.9">RDP report: loading...</div></body>',
|
||||
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)"
|
||||
|
||||
@@ -9,3 +9,5 @@ AW_SERVER_DATA_DIR=/var/lib/activitywatch
|
||||
AW_SERVER_LOG_DIR=/var/log/activitywatch
|
||||
AW_SERVER_USER=activitywatch
|
||||
AW_SERVER_GROUP=activitywatch
|
||||
AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610
|
||||
AW_WORKTIME_TZ=Europe/Moscow
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
AW = "http://127.0.0.1:5600/api/0"
|
||||
REPORT_TZ = ZoneInfo(os.environ.get("AW_WORKTIME_TZ", "Europe/Moscow"))
|
||||
|
||||
|
||||
def get(u):
|
||||
with urllib.request.urlopen(u, timeout=30) as r:
|
||||
return json.loads(r.read().decode())
|
||||
|
||||
|
||||
def pts(s):
|
||||
return datetime.fromisoformat(s.replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||
|
||||
|
||||
def report_today():
|
||||
now_local = datetime.now(REPORT_TZ)
|
||||
start_local = datetime(now_local.year, now_local.month, now_local.day, tzinfo=REPORT_TZ)
|
||||
end_local = start_local + timedelta(days=1) - timedelta(seconds=1)
|
||||
start = start_local.astimezone(timezone.utc)
|
||||
end = end_local.astimezone(timezone.utc)
|
||||
b = get(AW + "/buckets")
|
||||
sb = next((k for k in b if k.startswith("aw-worktime-sessions_")), None)
|
||||
if not sb:
|
||||
return []
|
||||
ev = get(f"{AW}/buckets/{sb}/events?limit=50000")
|
||||
by = {}
|
||||
for e in ev:
|
||||
ts = pts(e.get("timestamp"))
|
||||
if ts < start or ts > end:
|
||||
continue
|
||||
d = e.get("data") or {}
|
||||
user = (d.get("username") or "").strip()
|
||||
if not user:
|
||||
continue
|
||||
state = (d.get("state") or "").lower()
|
||||
active = ("актив" in state) or (state == "active")
|
||||
row = by.setdefault(user, {"active": set(), "first": None, "last": None, "rows": 0})
|
||||
row["rows"] += 1
|
||||
if active:
|
||||
second = ts.replace(microsecond=0)
|
||||
row["active"].add(second)
|
||||
row["first"] = second if row["first"] is None or second < row["first"] else row["first"]
|
||||
row["last"] = second if row["last"] is None or second > row["last"] else row["last"]
|
||||
rows = []
|
||||
full = int((end_local - start_local).total_seconds())
|
||||
for user in sorted(by):
|
||||
row = by[user]
|
||||
active_seconds = len(row["active"])
|
||||
rows.append({
|
||||
"user": user,
|
||||
"active_seconds": active_seconds,
|
||||
"active_hhmm": "%02d:%02d" % (active_seconds // 3600, (active_seconds % 3600) // 60),
|
||||
"first_activity": row["first"].isoformat().replace("+00:00", "Z") if row["first"] else "",
|
||||
"last_activity": row["last"].isoformat().replace("+00:00", "Z") if row["last"] else "",
|
||||
"idle_seconds": max(0, full - active_seconds),
|
||||
"sessions_count": row["rows"],
|
||||
})
|
||||
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"):
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
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()
|
||||
writer = csv.DictWriter(
|
||||
out,
|
||||
fieldnames=[
|
||||
"user",
|
||||
"active_seconds",
|
||||
"active_hhmm",
|
||||
"first_activity",
|
||||
"last_activity",
|
||||
"idle_seconds",
|
||||
"sessions_count",
|
||||
],
|
||||
)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
data = out.getvalue().encode()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/csv; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
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),
|
||||
"rows": rows,
|
||||
}
|
||||
data = json.dumps(obj, ensure_ascii=False, indent=2).encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
|
||||
HTTPServer(("0.0.0.0", 5610), H).serve_forever()
|
||||
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=AW Worktime Report API
|
||||
After=network.target activitywatch-server.service
|
||||
Wants=activitywatch-server.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/activitywatch/aw-server.env
|
||||
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-api.py
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
User=activitywatch
|
||||
Group=activitywatch
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -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: ' +
|
||||
'<a href="' + reportUrl + '" style="color:#fcd34d" target="_blank">HTML</a> | ' +
|
||||
'<a href="' + reportBase + '/reports/worktime/today?format=csv" style="color:#7dd3fc" target="_blank">CSV</a> | ' +
|
||||
'<a href="' + reportBase + '/reports/worktime/today" style="color:#86efac" target="_blank">JSON</a> | ' +
|
||||
'<a href="#" id="aw-report-toggle" style="color:#f9fafb">Panel</a>';
|
||||
|
||||
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 =
|
||||
'<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f172a;color:#fff;font:600 13px/1.2 sans-serif">' +
|
||||
'<div>RDP Worktime Report</div>' +
|
||||
'<div style="display:flex;gap:12px;align-items:center">' +
|
||||
'<a href="' + reportUrl + '" target="_blank" style="color:#93c5fd;text-decoration:none">Open</a>' +
|
||||
'<a href="#" id="aw-report-close" style="color:#fff;text-decoration:none">Close</a>' +
|
||||
"</div></div>" +
|
||||
'<iframe src="' + reportUrl + '" title="RDP Worktime Report" style="border:0;width:100%;height:calc(100% - 42px);background:#fff"></iframe>';
|
||||
|
||||
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);
|
||||
})();
|
||||
@@ -24,6 +24,8 @@ required_vars=(
|
||||
BOOTSTRAP_DIR="/root/bootstrap"
|
||||
VIEWS_JSON="$BOOTSTRAP_DIR/settings/views-default.json"
|
||||
CLASSES_JSON="$BOOTSTRAP_DIR/settings/classes-worktime.json"
|
||||
WORKTIME_API_SRC="$BOOTSTRAP_DIR/aw-worktime-api.py"
|
||||
WORKTIME_API_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-api.service"
|
||||
|
||||
for var_name in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var_name:-}" ]]; then
|
||||
@@ -89,6 +91,18 @@ systemctl enable activitywatch-server.service
|
||||
systemctl restart activitywatch-server.service
|
||||
systemctl --no-pager --full status activitywatch-server.service || true
|
||||
|
||||
if [[ -f "$WORKTIME_API_SRC" ]]; then
|
||||
install -m 0755 "$WORKTIME_API_SRC" /usr/local/bin/aw-worktime-api.py
|
||||
fi
|
||||
|
||||
if [[ -f "$WORKTIME_API_SERVICE_SRC" ]]; then
|
||||
install -m 0644 "$WORKTIME_API_SERVICE_SRC" /etc/systemd/system/aw-worktime-api.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable aw-worktime-api.service
|
||||
systemctl restart aw-worktime-api.service
|
||||
systemctl --no-pager --full status aw-worktime-api.service || true
|
||||
fi
|
||||
|
||||
for _ in $(seq 1 20); do
|
||||
if curl -fsS "http://127.0.0.1:${AW_SERVER_PORT}/api/0/info" >/dev/null 2>&1; then
|
||||
break
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json',
|
||||
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
|
||||
[string]$ServerHost,
|
||||
[int]$ServerPort,
|
||||
[ValidateSet('http', 'https')]
|
||||
@@ -758,12 +758,12 @@ $deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\ActivityWatch\dlp-policy.json' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\AWatch-rus\dlp-policy.json' }
|
||||
$resolvedPollSeconds = if ($PSBoundParameters.ContainsKey('PollSeconds')) { $PollSeconds } elseif ($deploymentConfig) { [int]$deploymentConfig.collector.pollSeconds } else { 5 }
|
||||
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\ActivityWatch\logs' }
|
||||
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\AWatch-rus\logs' }
|
||||
$resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("endpoint-signals-{0}.log" -f $env:USERNAME) }
|
||||
$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true }
|
||||
$resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'ActivityWatch-Phase2\\incident-artifacts' }
|
||||
$resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' }
|
||||
$resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true }
|
||||
|
||||
if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) {
|
||||
@@ -848,7 +848,7 @@ while ($true) {
|
||||
if ($script:SeenPrintJob.ContainsKey($jobId)) { continue }
|
||||
$script:SeenPrintJob[$jobId] = (Get-Date).ToUniversalTime()
|
||||
|
||||
$printerName = [string]$job.Name
|
||||
$printerName = Normalize-PrinterForMatch -Value ([string]$job.Name)
|
||||
$documentName = [string]$job.Document
|
||||
$owner = [string]$job.Owner
|
||||
$documentNameOriginal = $documentName
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json',
|
||||
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
|
||||
[string]$ServerHost,
|
||||
[int]$ServerPort,
|
||||
[ValidateSet('http', 'https')]
|
||||
@@ -497,9 +497,9 @@ $deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\ActivityWatch\dlp-policy.json' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\AWatch-rus\dlp-policy.json' }
|
||||
$resolvedPollSeconds = if ($PSBoundParameters.ContainsKey('PollSeconds')) { $PollSeconds } elseif ($deploymentConfig) { [int]$deploymentConfig.collector.pollSeconds } else { 10 }
|
||||
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\ActivityWatch\logs' }
|
||||
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\AWatch-rus\logs' }
|
||||
$resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("email-outbound-{0}.log" -f $env:USERNAME) }
|
||||
$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user