diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index b6038b1..d2095e5 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -11,6 +11,8 @@ aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}" aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}" + aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}" + aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}" tasks: - name: Install base packages @@ -143,6 +145,24 @@ delay: 3 until: aw_api.status == 200 + - name: Apply baseline worktime settings (classes) + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" + method: POST + body: "{{ aw_worktime_classes }}" + body_format: json + status_code: 201 + when: aw_apply_worktime_settings | default(false) | bool + + - name: Apply baseline views (include DLP and worktime) + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" + method: POST + body: "{{ aw_default_views }}" + body_format: json + status_code: 201 + when: aw_apply_worktime_settings | default(false) | bool + handlers: - name: Reload systemd ansible.builtin.systemd: diff --git a/ansible/group_vars/all.example.yml b/ansible/group_vars/all.example.yml index e94af15..fdf1b60 100644 --- a/ansible/group_vars/all.example.yml +++ b/ansible/group_vars/all.example.yml @@ -9,3 +9,7 @@ aw_server_user: "activitywatch" aw_server_group: "activitywatch" aw_repo_root: "/home/igor/tmp/AWatch-rus" + +# Optional: apply a baseline worktime-focused categorization and views via AW settings API. +# WARNING: this overwrites existing server-side settings/classes/views. +aw_apply_worktime_settings: false diff --git a/aw-server/settings/classes-worktime.json b/aw-server/settings/classes-worktime.json new file mode 100644 index 0000000..8b26019 --- /dev/null +++ b/aw-server/settings/classes-worktime.json @@ -0,0 +1,86 @@ +[ + { + "id": 0, + "name": ["Работа"], + "rule": { "type": "none" } + }, + { + "id": 1, + "name": ["Работа", "1С"], + "rule": { + "type": "regex", + "regex": "\\b(1cv8s?|1cv8c|1cestart)\\.exe\\b|1С:Предприятие|Запуск 1С:Предприятия|Загрузка конфигурационной информации|Доступ к информационной базе", + "ignore_case": true + }, + "data": { "color": "#194D33" } + }, + { + "id": 2, + "name": ["Работа", "Документы"], + "rule": { + "type": "regex", + "regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64)\\.exe\\b|Adobe Reader|Acrobat", + "ignore_case": true + }, + "data": { "color": "#2E7D32" } + }, + { + "id": 3, + "name": ["Работа", "Коммуникации"], + "rule": { + "type": "regex", + "regex": "\\b(teams|telegram|slack|thunderbird|zoom|skype|whatsapp|viber|discord)\\.exe\\b|Mattermost|Element|Riot", + "ignore_case": true + }, + "data": { "color": "#1E88E5" } + }, + { + "id": 4, + "name": ["Работа", "Администрирование"], + "rule": { + "type": "regex", + "regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd)\\.exe\\b", + "ignore_case": true + }, + "data": { "color": "#6D4C41" } + }, + { + "id": 5, + "name": ["Интернет"], + "rule": { "type": "none" } + }, + { + "id": 6, + "name": ["Интернет", "Браузер"], + "rule": { + "type": "regex", + "regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser)\\.exe\\b", + "ignore_case": true + }, + "data": { "color": "#00897B" } + }, + { + "id": 7, + "name": ["Система"], + "rule": { "type": "none" } + }, + { + "id": 8, + "name": ["Система", "Windows"], + "rule": { + "type": "regex", + "regex": "\\b(SearchHost|explorer|ShellExperienceHost|ApplicationFrameHost|RuntimeBroker|sihost|dwm|svchost|fontdrvhost|userinit)\\.exe\\b|\\\\Windows\\\\System32", + "ignore_case": true + }, + "data": { "color": "#607D8B" } + }, + { + "id": 9, + "name": ["ActivityWatch"], + "rule": { + "type": "regex", + "regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b", + "ignore_case": true + } + } +] diff --git a/aw-server/settings/views-default.json b/aw-server/settings/views-default.json new file mode 100644 index 0000000..5421e98 --- /dev/null +++ b/aw-server/settings/views-default.json @@ -0,0 +1,34 @@ +[ + { + "id": "summary", + "name": "Summary", + "elements": [ + { "type": "top_titles", "size": 3 }, + { "type": "timeline_barchart", "size": 3 }, + { "type": "top_categories", "size": 3 }, + { "type": "category_tree", "size": 3 } + ] + }, + { + "id": "window", + "name": "Window", + "elements": [ + { "type": "top_apps", "size": 3, "props": {} } + ] + }, + { + "id": "DLP", + "name": "DLP", + "elements": [] + }, + { + "id": "worktime", + "name": "Worktime", + "elements": [ + { "type": "top_categories", "size": 3, "props": {} }, + { "type": "timeline_barchart", "size": 3, "props": {} }, + { "type": "category_tree", "size": 3, "props": {} }, + { "type": "top_apps", "size": 3, "props": {} } + ] + } +] diff --git a/docs/runbook.md b/docs/runbook.md index ee83ca2..44f75c8 100755 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -34,6 +34,12 @@ ls -l /opt/activitywatch/webui-ru/js/ - `index.html` содержит оба include; - `service-worker.js` заменён cleanup-версией. +## Рабочее время (worktime) + +Если в деплое включен `aw_apply_worktime_settings: true`, playbook применяет базовую категоризацию (classes) и view `worktime`. + +Контрольные AQL-шаблоны для расчета рабочего времени: `docs/worktime_aql_detmir.md`. + ## Типовые инциденты ### Сервис не стартует diff --git a/docs/worktime_aql_detmir.md b/docs/worktime_aql_detmir.md new file mode 100644 index 0000000..3709bd2 --- /dev/null +++ b/docs/worktime_aql_detmir.md @@ -0,0 +1,70 @@ +# DetMir: Worktime (AQL templates) + +Цель: получить «рабочее время» по данным ActivityWatch с учетом: + +- активного времени (`not-afk`); +- категоризации (classes) для оконных событий; +- отдельного категоризованного веб-потока `aw-detmir-web-category_` для доменов. + +Ниже — шаблоны AQL для страницы `Query` в AW Web UI. + +## Рабочее время по приложениям (окна) + +Подходит для расчета рабочего времени в толстых клиентах (1С, документы, админка). + +```javascript +events = flood(query_bucket("aw-watcher-window_SHARKON2025")); +not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025")); +not_afk = filter_keyvals(not_afk, "status", ["not-afk"]); + +events = filter_period_intersect(events, not_afk); +events = categorize(events, __CATEGORIES__); + +work = filter_keyvals(events, "$category", [ + ["Работа", "1С"], + ["Работа", "Документы"], + ["Работа", "Коммуникации"], + ["Работа", "Администрирование"] +]); + +work = merge_events_by_keys(work, ["$category", "app"]); +RETURN = sort_by_duration(work); +``` + +## Рабочее время в вебе (по доменам) + +Требует, чтобы на клиенте работал browser collector и писал в: +`aw-detmir-web-category_` поля `categoryGroup`, `rootDomain`. + +```javascript +web = flood(query_bucket("aw-detmir-web-category_SHARKON2025")); +not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025")); +not_afk = filter_keyvals(not_afk, "status", ["not-afk"]); + +web = filter_period_intersect(web, not_afk); +web = filter_keyvals(web, "categoryGroup", ["work"]); + +web = merge_events_by_keys(web, ["rootDomain", "category"]); +RETURN = sort_by_duration(web); +``` + +## Sanity-check: «куда уходит время» + +```javascript +events = flood(query_bucket("aw-watcher-window_SHARKON2025")); +not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025")); +not_afk = filter_keyvals(not_afk, "status", ["not-afk"]); + +events = filter_period_intersect(events, not_afk); +events = categorize(events, __CATEGORIES__); + +events = merge_events_by_keys(events, ["$category"]); +RETURN = sort_by_duration(events); +``` + +## Замечания + +- Для других хостов замените суффикс `_SHARKON2025` на нужный hostname. +- Если web-поток пустой, рабочее время в браузере корректно посчитать по доменам не получится. Тогда либо: + - чинить/запускать browser collector; + - либо временно считать браузер в `window` как «Интернет/Браузер» без разделения на work/personal.