Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8988e5f7 | ||
|
|
230a9c6936 | ||
|
|
6a36febfeb | ||
|
|
e80272a55f | ||
|
|
1c3d7896cd | ||
|
|
211a6a6eac | ||
|
|
6f5e5eb751 | ||
|
|
e643576aa9 | ||
|
|
f5f4f84bef | ||
|
|
693e6832a6 | ||
|
|
538ff74611 | ||
|
|
b992ad2234 | ||
|
|
ac59d44719 | ||
|
|
f3c5e9ea53 | ||
|
|
38107b3c84 | ||
|
|
8ad02ae194 | ||
|
|
2fd0ca8eda | ||
|
|
9c59f74928 | ||
|
|
0c5069c255 | ||
|
|
f0db2a227b | ||
|
|
e37c3886ba | ||
|
|
429501d4fe | ||
|
|
3e8a6981f5 | ||
|
|
c9f3aad89c | ||
|
|
669501f20a | ||
|
|
cd5fd95faf | ||
|
|
5e5cb30fab | ||
|
|
8b92b1136f | ||
|
|
083b05d096 | ||
|
|
8571832615 | ||
|
|
eb25a230fc | ||
|
|
96ee17f156 | ||
|
|
66a8c47319 | ||
|
|
5a89a79803 | ||
|
|
a00bbdee06 | ||
|
|
9ef09cb892 | ||
|
|
8849354dda | ||
|
|
d91a396c00 | ||
|
|
8a91734a27 | ||
|
|
1df0e18a95 | ||
|
|
a6a05b82a9 | ||
|
|
03fefcc257 | ||
|
|
9eb02185dd | ||
|
|
b2a1a6289c | ||
|
|
945efd203f | ||
|
|
82ee9ab4fa | ||
|
|
eb83a8b08f | ||
|
|
7a89f33e3b | ||
|
|
d8320799e6 | ||
|
|
db1998c428 | ||
|
|
f6074facf5 | ||
|
|
3f3e3a953f | ||
|
|
d5735ba127 | ||
|
|
10c22a6c88 | ||
|
|
9e5cb144d4 | ||
|
|
d88ef78184 | ||
|
|
a35a8a51bd | ||
|
|
bfee99d679 | ||
|
|
5a2684fb3b | ||
|
|
e2b2f805fb | ||
|
|
eea851141f | ||
|
|
6578f6341f | ||
|
|
cc33ffa3dc | ||
|
|
ff548a5840 | ||
|
|
f916764d53 | ||
|
|
2bab84f9f9 | ||
|
|
6640299a48 | ||
|
|
3e565b7a2e | ||
|
|
3fa15f826d | ||
|
|
08ba731345 | ||
|
|
df497839f6 | ||
|
|
36e4255ad9 | ||
|
|
3971c459ef | ||
|
|
b7a7ac42e4 | ||
|
|
4359f6d5eb | ||
|
|
7ea4ebd463 | ||
|
|
8f10bef904 | ||
|
|
aca42caf0a | ||
|
|
9bdf28ed2b |
+11236
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
created: 2026-05-07T21:46:00Z
|
||||
title: Deploy standalone agent on SHARKON2025 and validate live flow
|
||||
area: tooling
|
||||
files:
|
||||
- windows/installkit/innosetup/AWatch-rus-InnoSetup.iss
|
||||
- windows/install-standalone-service.ps1
|
||||
- windows/aw-standalone-service.ps1
|
||||
- windows/installkit/innosetup/BUILD.md
|
||||
- docs/windows/deployment.md
|
||||
- docs/windows/troubleshooting.md
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Standalone InnoSetup mode and service wrapper are implemented and pushed, and AW API write path was verified by manual heartbeat posts. But production value still depends on real endpoint rollout: installer must be deployed on SHARKON2025 (192.168.100.21), service must be running persistently, and UI/API must show continuously fresh events without manual seeding.
|
||||
|
||||
## Solution
|
||||
|
||||
Deploy the newly built installer `AWatch-rus-InstallKit.exe` to SHARKON2025, run installation with target `10.10.10.13:5600`, verify `AWatchRusStandaloneAgent` state, inspect `standalone-agent-service.log`, and confirm fresh `metadata.end` progression for `aw-dlp-endpoint-signals_SHARKON2025`, `aw-file-operations_SHARKON2025`, and `aw-worktime-sessions_SHARKON2025` over time.
|
||||
@@ -5,6 +5,7 @@
|
||||
## Что входит
|
||||
|
||||
- `docs/preparation.md` — подготовка инфраструктуры и входных параметров.
|
||||
- `docs/codebase-onboarding.md` — обзор структуры репозитория и маршрут изучения для новичка.
|
||||
- `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server.
|
||||
- `docs/runbook.md` — быстрый runbook для оператора.
|
||||
- `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback.
|
||||
@@ -14,6 +15,8 @@
|
||||
- `docs/console-ssh-logger.md` — логирование только консольных команд и SSH-сессий в AW.
|
||||
- `docs/dlp-gap-analysis.md` — разрыв до enterprise DLP и roadmap.
|
||||
- `docs/dlp-aggregator.md` — прототип централизованной агрегации DLP/file-operation событий.
|
||||
- `docs/dlp-reliability-roadmap.md` — roadmap повышения надёжности DLP-коллекторов.
|
||||
- `docs/powershell-analysis.md` — статический анализ работоспособности DLP PowerShell-скриптов.
|
||||
- `proxmox/` — шаблонные скрипты подготовки и наполнения CT на стороне Proxmox.
|
||||
- `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI.
|
||||
- `ansible/` — Ansible-ensemble для автоматизированного сервера (Debian/CT).
|
||||
|
||||
+184
-19
@@ -254,8 +254,131 @@
|
||||
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
|
||||
ansible.builtin.file:
|
||||
path: /root/bootstrap
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков)
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "{{ item.mode }}"
|
||||
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 скрипт
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh"
|
||||
dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh
|
||||
mode: "0755"
|
||||
|
||||
- name: Записать /etc/activitywatch/aw-server.env перед хотфиксами
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/activitywatch/aw-server.env
|
||||
mode: "0640"
|
||||
owner: root
|
||||
group: root
|
||||
content: |
|
||||
AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
|
||||
AW_SERVER_PORT={{ aw_server_port }}
|
||||
AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
|
||||
AW_SERVER_DB_PATH={{ aw_server_db_path }}
|
||||
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
|
||||
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: Установить скрипт AW worktime UI bridge
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.py"
|
||||
dest: /usr/local/bin/aw-worktime-ui-bridge.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить systemd unit AW worktime UI bridge
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service"
|
||||
dest: /etc/systemd/system/aw-worktime-ui-bridge.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Установить systemd timer AW worktime UI bridge
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.timer"
|
||||
dest: /etc/systemd/system/aw-worktime-ui-bridge.timer
|
||||
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: Отключить legacy timer aw-worktime-afk-bridge (если есть)
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-afk-bridge.timer
|
||||
enabled: false
|
||||
state: stopped
|
||||
failed_when: false
|
||||
|
||||
- name: Включить и перезапустить AW worktime UI bridge timer
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-ui-bridge.timer
|
||||
enabled: true
|
||||
state: restarted
|
||||
|
||||
- name: Выполнить разовый прогон AW worktime UI bridge
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-ui-bridge.service
|
||||
state: started
|
||||
failed_when: false
|
||||
|
||||
- name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
|
||||
ansible.builtin.command:
|
||||
cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh"
|
||||
register: apply_ru_patch_result
|
||||
failed_when: false
|
||||
|
||||
- name: Вывести результат применения хотфиксов
|
||||
ansible.builtin.debug:
|
||||
msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}"
|
||||
|
||||
- name: Проверить наличие index.html после копирования
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
@@ -285,24 +408,6 @@
|
||||
regexp: '</body>'
|
||||
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
|
||||
|
||||
- name: Записать /etc/activitywatch/aw-server.env
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/activitywatch/aw-server.env
|
||||
mode: "0640"
|
||||
owner: root
|
||||
group: root
|
||||
content: |
|
||||
AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
|
||||
AW_SERVER_PORT={{ aw_server_port }}
|
||||
AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
|
||||
AW_SERVER_DB_PATH={{ aw_server_db_path }}
|
||||
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
|
||||
AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
|
||||
AW_SERVER_USER={{ aw_server_user }}
|
||||
AW_SERVER_GROUP={{ aw_server_group }}
|
||||
XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
|
||||
XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
|
||||
|
||||
- name: Скопировать merge script AW DB на сервер
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/scripts/merge_aw_server_dbs.py"
|
||||
@@ -451,7 +556,7 @@
|
||||
register: aw_classes_current
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Сохранить backup текущих server-side settings/views/classes
|
||||
- name: Создать backup текущих server-side settings/views/classes
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ aw_server_data_dir }}/backups/{{ item.name }}-{{ ansible_date_time.iso8601_basic_short }}.json"
|
||||
owner: "{{ aw_server_user }}"
|
||||
@@ -467,6 +572,66 @@
|
||||
payload: "{{ aw_classes_current.json | default(none) }}"
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Настроить DLP Aggregator (Phase 2)
|
||||
block:
|
||||
- name: Создать каталог для скриптов
|
||||
ansible.builtin.file:
|
||||
path: "/opt/activitywatch/scripts"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Скопировать агрегатор событий DLP
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/scripts/aggregate_dlp_events.py"
|
||||
dest: "/opt/activitywatch/scripts/aggregate_dlp_events.py"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить systemd unit для агрегатора
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/activitywatch-dlp-aggregator.service
|
||||
content: |
|
||||
[Unit]
|
||||
Description=ActivityWatch DLP Event Aggregator
|
||||
After=activitywatch-server.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User={{ aw_server_user }}
|
||||
WorkingDirectory={{ aw_server_data_dir }}
|
||||
ExecStart=/usr/bin/python3 /opt/activitywatch/scripts/aggregate_dlp_events.py \
|
||||
--aw-url http://127.0.0.1:{{ aw_server_port }}/api/0 \
|
||||
--sqlite-path {{ aw_server_data_dir }}/dlp_warehouse.sqlite \
|
||||
--state-path {{ aw_server_data_dir }}/dlp-aggregator-state.json
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
- name: Установить systemd timer для агрегатора
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/activitywatch-dlp-aggregator.timer
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Run ActivityWatch DLP Aggregator every 5 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=1s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
- name: Включить и запустить таймер агрегатора
|
||||
ansible.builtin.systemd:
|
||||
name: activitywatch-dlp-aggregator.timer
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
- name: Применить базовые worktime settings (classes)
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
- ActivityWatch.Windows.Common.psm1
|
||||
- browser-domains-native-collector.ps1
|
||||
- dlp-endpoint-signals-collector.ps1
|
||||
- email-outbound-collector.ps1
|
||||
- file-operations-collector.ps1
|
||||
- worktime-session-collector.ps1
|
||||
- migrate-awatch-rus-paths.ps1
|
||||
@@ -159,6 +160,9 @@
|
||||
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
|
||||
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
|
||||
{% endif %}
|
||||
{% if (aw_windows_hostname_override | default('') | string | length) > 0 %}
|
||||
$params.AwHostname = "{{ aw_windows_hostname_override }}"
|
||||
{% endif %}
|
||||
{% if aw_windows_skip_hardening | bool %}
|
||||
$params.SkipHardening = $true
|
||||
{% endif %}
|
||||
@@ -182,6 +186,44 @@
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-CollectorKey {
|
||||
param([string]$CommandLine)
|
||||
if (-not $CommandLine) { return $null }
|
||||
$cl = $CommandLine.ToLowerInvariant()
|
||||
if ($cl -like '*browser-domains-native-collector.ps1*') { return 'browser' }
|
||||
if ($cl -like '*file-operations-collector.ps1*') { return 'fileops' }
|
||||
if ($cl -like '*dlp-endpoint-signals-collector.ps1*') { return 'endpoint' }
|
||||
if ($cl -like '*email-outbound-collector.ps1*') { return 'email' }
|
||||
if ($cl -like '*worktime-session-collector.ps1*') { return 'worktime' }
|
||||
return $null
|
||||
}
|
||||
|
||||
$collectorProcs = Get-CimInstance Win32_Process |
|
||||
Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine } |
|
||||
ForEach-Object {
|
||||
$key = Get-CollectorKey -CommandLine $_.CommandLine
|
||||
if ($key) {
|
||||
[pscustomobject]@{
|
||||
ProcessId = [int]$_.ProcessId
|
||||
SessionId = [int]$_.SessionId
|
||||
CreationDate = $_.CreationDate
|
||||
CollectorKey = $key
|
||||
}
|
||||
}
|
||||
} |
|
||||
Where-Object { $_ -ne $null }
|
||||
|
||||
# Keep only one process per (collector, session): newest survives, older duplicates are stopped.
|
||||
foreach ($group in ($collectorProcs | Group-Object CollectorKey, SessionId)) {
|
||||
$ordered = @($group.Group | Sort-Object CreationDate -Descending)
|
||||
if ($ordered.Count -le 1) { continue }
|
||||
foreach ($dup in $ordered | Select-Object -Skip 1) {
|
||||
Stop-Process -Id $dup.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
||||
Get-ScheduledTask |
|
||||
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
|
||||
@@ -201,7 +243,12 @@
|
||||
- aw_windows_afk_enabled | bool
|
||||
- aw_windows_hostname_result.stdout is defined
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_api_smoke_check_bucket_effective: "aw-watcher-afk_{{ aw_windows_hostname_result.stdout | trim }}"
|
||||
aw_windows_api_smoke_check_bucket_effective: >-
|
||||
{{
|
||||
aw_windows_api_smoke_check_bucket
|
||||
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
||||
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
|
||||
}}
|
||||
|
||||
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
|
||||
when:
|
||||
@@ -234,7 +281,7 @@
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Стянуть отчёт валидации с эндпоинта
|
||||
ansible.windows.win_fetch:
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ aw_windows_validation_remote_path }}"
|
||||
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
flat: true
|
||||
|
||||
@@ -8,6 +8,8 @@ aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-
|
||||
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 }}"
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@ aw_server_bind_host: "0.0.0.0"
|
||||
aw_server_port: 5600
|
||||
aw_server_webui_dir: "/opt/activitywatch/webui-ru"
|
||||
aw_server_data_dir: "/var/lib/activitywatch"
|
||||
aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db"
|
||||
aw_server_log_dir: "/var/log/activitywatch"
|
||||
aw_server_db_path: "/var/lib/activitywatch/aw-server-rust/sqlite.db"
|
||||
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 }}"
|
||||
|
||||
# Optional: apply worktime settings via server-side settings API.
|
||||
aw_apply_worktime_settings: true
|
||||
aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian"
|
||||
|
||||
aw_server_cors_origins:
|
||||
- "http://127.0.0.1:5600"
|
||||
- "http://localhost:5600"
|
||||
- "http://10.10.10.13:5600"
|
||||
- "http://aw-server:5600"
|
||||
- "http://192.168.100.13:5600"
|
||||
- "http://snb-live:5600"
|
||||
|
||||
aw_apply_worktime_settings: true
|
||||
|
||||
aw_worktime_from: "08:00"
|
||||
aw_worktime_to: "17:00"
|
||||
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
|
||||
aw_server_always_active_pattern: "aw-watcher-window"
|
||||
aw_server_landingpage: "/activity/SHARKON2025/view/"
|
||||
|
||||
@@ -24,9 +24,13 @@ aw_windows_extra_users: []
|
||||
|
||||
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
aw_windows_hostname_override: ""
|
||||
|
||||
aw_windows_afk_enabled: true
|
||||
aw_windows_window_enabled: true
|
||||
aw_windows_file_ops_enabled: true
|
||||
aw_windows_email_collector_enabled: true
|
||||
aw_windows_email_collector_mode: "both"
|
||||
aw_windows_local_agent_logs_enabled: false
|
||||
aw_windows_incident_capture_enabled: true
|
||||
aw_windows_incident_screenshot_enabled: true
|
||||
@@ -41,7 +45,7 @@ aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ans
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||||
aw_windows_fail_on_validation_error: true
|
||||
|
||||
aw_windows_migration_enabled: true
|
||||
aw_windows_migration_enabled: false
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
@@ -49,4 +53,3 @@ aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migrat
|
||||
aw_windows_api_smoke_check_enabled: true
|
||||
aw_windows_api_smoke_check_bucket: ""
|
||||
aw_windows_api_smoke_check_limit: 10
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ aw_windows_extra_users: []
|
||||
# Единые Windows/RDP пути: те же, что использует InnoSetup.
|
||||
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
aw_windows_hostname_override: "" # Например: SHARKON2025
|
||||
aw_windows_afk_enabled: true
|
||||
aw_windows_window_enabled: true
|
||||
aw_windows_file_ops_enabled: true
|
||||
aw_windows_local_agent_logs_enabled: false
|
||||
aw_windows_incident_capture_enabled: true
|
||||
aw_windows_incident_screenshot_enabled: true
|
||||
|
||||
+2
-11
@@ -1,14 +1,5 @@
|
||||
[proxmox]
|
||||
# Optional. Leave empty if you don't use Proxmox provisioning from this repo.
|
||||
# pve-main ansible_host=10.10.10.2 ansible_user=igor ansible_port=22
|
||||
|
||||
[aw_server]
|
||||
aw-server ansible_host=10.10.10.13 ansible_user=igor ansible_port=22
|
||||
localhost ansible_connection=local ansible_user=root
|
||||
|
||||
[aw_windows]
|
||||
# Note: on RU-localized Windows the built-in admin account name is often "Администратор".
|
||||
rdp-prod ansible_host=192.168.100.21 ansible_user=Администратор ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
|
||||
|
||||
[aw_pfsense_pollers]
|
||||
# Optional.
|
||||
# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22
|
||||
rdp-prod ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=Sergei2009@ ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||||
aw_windows_server_scheme: "http"
|
||||
aw_windows_server_host: "10.10.10.13"
|
||||
aw_windows_server_port: 5600
|
||||
aw_windows_fail_on_validation_error: true
|
||||
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
|
||||
aw_windows_recovery_task_name: "ActivityWatch Recovery"
|
||||
aw_windows_force_task_restart: true
|
||||
@@ -44,6 +52,7 @@
|
||||
ansible.builtin.uri:
|
||||
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: true
|
||||
register: aw_windows_api_smoke
|
||||
until: >
|
||||
@@ -87,4 +96,3 @@
|
||||
msg:
|
||||
- "Validation OK on {{ inventory_hostname }}."
|
||||
- "Report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
- install_aw_server.sh
|
||||
- apply_webui_ru_patch.sh
|
||||
- activitywatch-server.service
|
||||
- aw-worktime-api.py
|
||||
- aw-worktime-api.service
|
||||
- aw-worktime-ui-bridge.py
|
||||
- aw-worktime-ui-bridge.service
|
||||
- aw-worktime-ui-bridge.timer
|
||||
- aw-worktime-panel.js
|
||||
- aw-server.env.example
|
||||
- aw-ru-patch.js
|
||||
- aw-sw-cleanup.js
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
- install_aw_server.sh
|
||||
- apply_webui_ru_patch.sh
|
||||
- activitywatch-server.service
|
||||
- aw-worktime-api.py
|
||||
- aw-worktime-api.service
|
||||
- aw-worktime-ui-bridge.py
|
||||
- aw-worktime-ui-bridge.service
|
||||
- aw-worktime-ui-bridge.timer
|
||||
- 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,39 +10,97 @@ 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)'
|
||||
TIMESPIRAL_NEEDLE='start:new Date("2022-08-08")'
|
||||
TIMESPIRAL_REPLACEMENT='start:new Date(Date.now()-12*36e5)'
|
||||
CATEGORY_HELPER_NEEDLE='hostname:t.hostnameChoices[0]'
|
||||
CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){return"unknown"!==t}))[0]||t.hostnameChoices[0]'
|
||||
CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){return"unknown"!==t&&"undefined"!==t}))[0]||t.hostnameChoices[0]'
|
||||
|
||||
[[ -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)"
|
||||
|
||||
+40
-14
@@ -370,6 +370,16 @@
|
||||
return /^pve[-_]/i.test(String(host || ""));
|
||||
}
|
||||
|
||||
function isLikelyClientHost(host) {
|
||||
const value = String(host || "").trim();
|
||||
if (!value) return false;
|
||||
if (/^(?:unknown|undefined|null)$/i.test(value)) return false;
|
||||
if (/^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)$/i.test(value)) return false;
|
||||
if (/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) return false;
|
||||
if (value.indexOf(":") !== -1 && /^[0-9a-f:\[\]]+$/i.test(value)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function enforceSafeActivityViewForPveHost() {
|
||||
const hash = window.location.hash || "";
|
||||
const match = hash.match(/^#\/activity\/([^/]+)\/day\/([^/]+)\/view\/([^/?#]+)/i);
|
||||
@@ -386,9 +396,9 @@
|
||||
|
||||
function getDlpHostFromSettings(settings) {
|
||||
const routeHost = getCurrentHostFromHash();
|
||||
if (routeHost) return routeHost;
|
||||
if (isLikelyClientHost(routeHost)) return routeHost;
|
||||
const bucketHost = getDlpHostFromBucketId(getDlpBucketIdFromHash());
|
||||
if (bucketHost) return bucketHost;
|
||||
if (isLikelyClientHost(bucketHost)) return bucketHost;
|
||||
return getTrendsHostFromSettings(settings);
|
||||
}
|
||||
|
||||
@@ -1466,7 +1476,8 @@
|
||||
if (!settings || typeof settings !== "object") return "";
|
||||
const landingpage = typeof settings.landingpage === "string" ? settings.landingpage : "";
|
||||
const match = landingpage.match(/\/activity\/([^/]+)/);
|
||||
return match && match[1] ? match[1] : "";
|
||||
const host = match && match[1] ? decodeURIComponent(match[1]) : "";
|
||||
return isLikelyClientHost(host) ? host : "";
|
||||
}
|
||||
|
||||
function getTrendsPath(hash) {
|
||||
@@ -1533,22 +1544,35 @@
|
||||
.map(function (bucketId) { return bucketId.replace(/^aw-watcher-window_/i, ""); })
|
||||
.filter(Boolean)
|
||||
.filter(function (host) { return !/^unknown$/i.test(host); });
|
||||
if (settingsHost && hosts.indexOf(settingsHost) >= 0) return settingsHost;
|
||||
if (settingsHost) return settingsHost;
|
||||
if (isLikelyClientHost(settingsHost) && hosts.indexOf(settingsHost) >= 0) return settingsHost;
|
||||
hosts.sort();
|
||||
return hosts[0] || "";
|
||||
}
|
||||
|
||||
function rewriteUnknownCategoryBuilderQueryBody(body) {
|
||||
if (typeof body !== "string") return body;
|
||||
if (body.indexOf("aw-watcher-window_unknown") === -1 && body.indexOf("aw-watcher-afk_unknown") === -1) {
|
||||
return body;
|
||||
if (body.indexOf("undefined") !== -1) {
|
||||
body = body
|
||||
.replace(/flood\(query_bucket\(find_bucket\(\\"undefined\\"\)\)\)/g, '[]')
|
||||
.replace(/query_bucket\(find_bucket\(\\"undefined\\"\)\)/g, '[]')
|
||||
.replace(/flood\(query_bucket\(\\"undefined\\"\)\)/g, '[]')
|
||||
.replace(/query_bucket\(\\"undefined\\"\)/g, '[]');
|
||||
const ph = getPreferredWindowHostFromBuckets();
|
||||
if (ph) {
|
||||
body = body
|
||||
.replace(/aw-watcher-window_undefined/g, "aw-watcher-window_" + ph)
|
||||
.replace(/aw-watcher-afk_undefined/g, "aw-watcher-afk_" + ph);
|
||||
}
|
||||
}
|
||||
const preferredHost = getPreferredWindowHostFromBuckets();
|
||||
if (!preferredHost) return body;
|
||||
return body
|
||||
.replace(/aw-watcher-window_unknown/g, "aw-watcher-window_" + preferredHost)
|
||||
.replace(/aw-watcher-afk_unknown/g, "aw-watcher-afk_" + preferredHost);
|
||||
if (body.indexOf("aw-watcher-window_unknown") !== -1 || body.indexOf("aw-watcher-afk_unknown") !== -1) {
|
||||
const preferredHost = getPreferredWindowHostFromBuckets();
|
||||
if (preferredHost) {
|
||||
body = body
|
||||
.replace(/aw-watcher-window_unknown/g, "aw-watcher-window_" + preferredHost)
|
||||
.replace(/aw-watcher-afk_unknown/g, "aw-watcher-afk_" + preferredHost);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
function installCategoryBuilderNetworkPatch() {
|
||||
@@ -1560,7 +1584,8 @@
|
||||
window.fetch = function (input, init) {
|
||||
try {
|
||||
const url = typeof input === "string" ? input : String(input && input.url || "");
|
||||
if (/\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
|
||||
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
|
||||
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
|
||||
init = Object.assign({}, init, {
|
||||
body: rewriteUnknownCategoryBuilderQueryBody(init.body)
|
||||
});
|
||||
@@ -1584,7 +1609,8 @@
|
||||
proto.send = function (body) {
|
||||
try {
|
||||
const url = String(this.__awRuUrl || "");
|
||||
if (/\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
|
||||
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
|
||||
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
|
||||
body = rewriteUnknownCategoryBuilderQueryBody(body);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
AW_URL = os.environ.get("AW_SERVER_URL", "http://127.0.0.1:5600")
|
||||
HOST = os.environ.get("AW_WORKTIME_HOST", "SHARKON2025")
|
||||
STATE_PATH = os.environ.get(
|
||||
"AW_WORKTIME_UI_BRIDGE_STATE",
|
||||
"/var/lib/activitywatch/aw-worktime-ui-bridge-state.json",
|
||||
)
|
||||
TIMEOUT = float(os.environ.get("AW_WORKTIME_UI_BRIDGE_TIMEOUT", "20"))
|
||||
|
||||
|
||||
SESSIONS_BUCKET = f"aw-worktime-sessions_{HOST}"
|
||||
AFK_BUCKET = f"aw-watcher-afk_{HOST}"
|
||||
WINDOW_BUCKET = f"aw-watcher-window_{HOST}"
|
||||
|
||||
|
||||
def _req(method: str, path: str, payload=None):
|
||||
data = None
|
||||
headers = {}
|
||||
if payload is not None:
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(AW_URL + path, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
|
||||
raw = r.read()
|
||||
if not raw:
|
||||
return None
|
||||
return json.loads(raw.decode("utf-8"))
|
||||
|
||||
|
||||
def ensure_bucket(bucket_id: str, event_type: str, client: str):
|
||||
payload = {"client": client, "type": event_type, "hostname": HOST}
|
||||
try:
|
||||
_req("POST", f"/api/0/buckets/{bucket_id}", payload)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 304:
|
||||
raise
|
||||
|
||||
|
||||
def load_state():
|
||||
try:
|
||||
with open(STATE_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict) and "last_ts" in data:
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return {"last_ts": "1970-01-01T00:00:00Z"}
|
||||
|
||||
|
||||
def save_state(state):
|
||||
os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True)
|
||||
tmp = STATE_PATH + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, ensure_ascii=False)
|
||||
os.replace(tmp, STATE_PATH)
|
||||
|
||||
|
||||
def to_iso_utc(ts):
|
||||
if ts.endswith("Z"):
|
||||
return ts
|
||||
return ts.replace("+00:00", "Z")
|
||||
|
||||
|
||||
def parse_iso_utc(ts: str):
|
||||
if ts.endswith("Z"):
|
||||
ts = ts[:-1] + "+00:00"
|
||||
return datetime.fromisoformat(ts).astimezone(timezone.utc)
|
||||
|
||||
|
||||
def build_window_title(users, active_count):
|
||||
if not users:
|
||||
return "RDP idle"
|
||||
return f"RDP active ({active_count}): " + ", ".join(users)
|
||||
|
||||
|
||||
def _is_session_active(row_data):
|
||||
if isinstance(row_data.get("active"), bool):
|
||||
return row_data.get("active")
|
||||
state = str(row_data.get("state", "")).strip().lower()
|
||||
return state in {"active", "активно"}
|
||||
|
||||
|
||||
def transform(events):
|
||||
out_afk = []
|
||||
out_win = []
|
||||
last_ts = None
|
||||
|
||||
grouped = {}
|
||||
for e in events:
|
||||
ts = e.get("timestamp")
|
||||
if not ts:
|
||||
continue
|
||||
grouped.setdefault(ts, []).append(e)
|
||||
|
||||
for ts in sorted(grouped.keys()):
|
||||
rows = grouped[ts]
|
||||
duration = max(float(r.get("duration", 0.0)) for r in rows)
|
||||
active_users = []
|
||||
for r in rows:
|
||||
data = r.get("data") or {}
|
||||
user = str(data.get("username", "")).strip()
|
||||
if user and _is_session_active(data):
|
||||
active_users.append(user)
|
||||
active_users = sorted(set(active_users))
|
||||
active_count = len(active_users)
|
||||
is_active = active_count > 0
|
||||
|
||||
afk_data = {"status": "not-afk" if is_active else "afk", "source": "aw-worktime-ui-bridge"}
|
||||
out_afk.append({"timestamp": ts, "duration": duration, "data": afk_data})
|
||||
|
||||
win_data = {
|
||||
"app": "RDP",
|
||||
"title": build_window_title(active_users, active_count),
|
||||
"source": "aw-worktime-ui-bridge",
|
||||
}
|
||||
out_win.append({"timestamp": ts, "duration": duration, "data": win_data})
|
||||
last_ts = ts
|
||||
|
||||
return out_afk, out_win, last_ts
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
last_ts = state.get("last_ts", "1970-01-01T00:00:00Z")
|
||||
|
||||
ensure_bucket(AFK_BUCKET, "afkstatus", "aw-worktime-ui-bridge")
|
||||
ensure_bucket(WINDOW_BUCKET, "currentwindow", "aw-worktime-ui-bridge")
|
||||
|
||||
now_utc = datetime.now(timezone.utc)
|
||||
recent = _req("GET", f"/api/0/buckets/{SESSIONS_BUCKET}/events?limit=5000") or []
|
||||
if not recent:
|
||||
return
|
||||
|
||||
try:
|
||||
last_dt = parse_iso_utc(last_ts)
|
||||
except Exception:
|
||||
last_dt = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
|
||||
events = []
|
||||
for e in recent:
|
||||
ts = e.get("timestamp")
|
||||
if not ts:
|
||||
continue
|
||||
try:
|
||||
if parse_iso_utc(ts) > last_dt:
|
||||
events.append(e)
|
||||
except Exception:
|
||||
continue
|
||||
if not events:
|
||||
return
|
||||
|
||||
afk_events, win_events, new_last_ts = transform(events)
|
||||
if not afk_events or not win_events or not new_last_ts:
|
||||
return
|
||||
|
||||
_req("POST", f"/api/0/buckets/{AFK_BUCKET}/events", afk_events)
|
||||
_req("POST", f"/api/0/buckets/{WINDOW_BUCKET}/events", win_events)
|
||||
save_state({"last_ts": new_last_ts})
|
||||
print(f"posted_afk={len(afk_events)} posted_win={len(win_events)} last_ts={new_last_ts}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=AW Worktime UI bridge (sessions -> afk/window)
|
||||
After=network-online.target activitywatch-server.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=AW_SERVER_URL=http://127.0.0.1:5600
|
||||
Environment=AW_WORKTIME_HOST=SHARKON2025
|
||||
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-ui-bridge.py
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run AW Worktime UI bridge every 30 seconds
|
||||
|
||||
[Timer]
|
||||
OnBootSec=20s
|
||||
OnUnitActiveSec=30s
|
||||
Unit=aw-worktime-ui-bridge.service
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -24,6 +24,11 @@ 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"
|
||||
WORKTIME_UI_BRIDGE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.py"
|
||||
WORKTIME_UI_BRIDGE_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.service"
|
||||
WORKTIME_UI_BRIDGE_TIMER_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.timer"
|
||||
|
||||
for var_name in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var_name:-}" ]]; then
|
||||
@@ -89,6 +94,36 @@ 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
|
||||
|
||||
if [[ -f "$WORKTIME_UI_BRIDGE_SRC" ]]; then
|
||||
install -m 0755 "$WORKTIME_UI_BRIDGE_SRC" /usr/local/bin/aw-worktime-ui-bridge.py
|
||||
fi
|
||||
|
||||
if [[ -f "$WORKTIME_UI_BRIDGE_SERVICE_SRC" ]]; then
|
||||
install -m 0644 "$WORKTIME_UI_BRIDGE_SERVICE_SRC" /etc/systemd/system/aw-worktime-ui-bridge.service
|
||||
fi
|
||||
|
||||
if [[ -f "$WORKTIME_UI_BRIDGE_TIMER_SRC" ]]; then
|
||||
install -m 0644 "$WORKTIME_UI_BRIDGE_TIMER_SRC" /etc/systemd/system/aw-worktime-ui-bridge.timer
|
||||
systemctl daemon-reload
|
||||
systemctl disable --now aw-worktime-afk-bridge.timer >/dev/null 2>&1 || true
|
||||
systemctl enable aw-worktime-ui-bridge.timer
|
||||
systemctl restart aw-worktime-ui-bridge.timer
|
||||
systemctl start aw-worktime-ui-bridge.service || true
|
||||
systemctl --no-pager --full status aw-worktime-ui-bridge.timer || 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
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# check-aw-data.sh - Проверка сбора данных ActivityWatch с RDP-сервера SHARKON2025
|
||||
# Сервер: 10.10.10.13:5600
|
||||
# Хост-источник: 192.168.100.21 (SHARKON2025)
|
||||
|
||||
SERVER="http://10.10.10.13:5600"
|
||||
HOSTNAME_FILTER="SHARKON2025"
|
||||
NOW=$(date -u +%s)
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Проверка доступности сервера
|
||||
echo "=== ActivityWatch Data Check: $HOSTNAME_FILTER ==="
|
||||
echo ""
|
||||
echo -n "Server connectivity... "
|
||||
RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
|
||||
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
|
||||
VERSION=$(echo "$RESP" | jq -r '.version')
|
||||
echo -e "${GREEN}OK${NC} (aw-server v$VERSION)"
|
||||
else
|
||||
echo -e "${RED}FAILED${NC} (cannot reach $SERVER)"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Проверка бакетов
|
||||
echo "--- Buckets ---"
|
||||
printf "%-45s %-8s %-22s %s\n" "BUCKET" "EVENTS" "LAST EVENT" "STATUS"
|
||||
printf "%-45s %-8s %-22s %s\n" "---------------------------------------------" "--------" "----------------------" "------"
|
||||
|
||||
BUCKETS=(
|
||||
"aw-dlp-endpoint-signals"
|
||||
"aw-dlp-incidents"
|
||||
"aw-dlp-review"
|
||||
"aw-dlp-rules"
|
||||
"aw-watcher-afk"
|
||||
"aw-watcher-window"
|
||||
"aw-session-events"
|
||||
"aw-worktime-sessions"
|
||||
)
|
||||
|
||||
for bucket in "${BUCKETS[@]}"; do
|
||||
bucket_full="${bucket}_${HOSTNAME_FILTER}"
|
||||
|
||||
# Получаем последний event
|
||||
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
|
||||
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
|
||||
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
|
||||
|
||||
# Вычисляем возраст
|
||||
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
|
||||
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
|
||||
if [ "$EVENT_EPOCH" -gt 0 ]; then
|
||||
AGE_SEC=$((NOW - EVENT_EPOCH))
|
||||
if [ $AGE_SEC -lt 3600 ]; then
|
||||
AGE="$((AGE_SEC / 60))m ago"
|
||||
STATUS="${GREEN}FRESH${NC}"
|
||||
elif [ $AGE_SEC -lt 86400 ]; then
|
||||
AGE="$((AGE_SEC / 3600))h ago"
|
||||
STATUS="${YELLOW}STALE${NC}"
|
||||
else
|
||||
AGE="$((AGE_SEC / 86400))d ago"
|
||||
STATUS="${RED}DEAD${NC}"
|
||||
fi
|
||||
else
|
||||
AGE="unknown"
|
||||
STATUS="${RED}?${NC}"
|
||||
fi
|
||||
else
|
||||
AGE="none"
|
||||
LAST_ID="0"
|
||||
STATUS="${RED}EMPTY${NC}"
|
||||
fi
|
||||
|
||||
printf "%-45s %-8s %-22s %b\n" "$bucket_full" "$LAST_ID" "$LAST_TS ($AGE)" "$STATUS"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Проверка CORS
|
||||
echo "--- CORS Check ---"
|
||||
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
|
||||
if [ "$CORS_RESP" = "200" ]; then
|
||||
echo -e "${GREEN}CORS: OK${NC} (HTTP 200)"
|
||||
else
|
||||
echo -e "${RED}CORS: FAIL${NC} (HTTP $CORS_RESP)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Check Complete ==="
|
||||
echo "Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
# check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост
|
||||
# Сервер: 10.10.10.13:5600
|
||||
# RDP-хост: 192.168.100.21 (SHARKON2025)
|
||||
|
||||
SERVER="http://10.10.10.13:5600"
|
||||
HOSTNAME_FILTER="SHARKON2025"
|
||||
RDP_HOST="192.168.100.21"
|
||||
NOW=$(date -u +%s)
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${CYAN}=== ActivityWatch Full Check: $HOSTNAME_FILTER ===${NC}"
|
||||
echo ""
|
||||
|
||||
# 1. Проверка сервера
|
||||
echo -e "${CYAN}--- 1. AW Server ($SERVER) ---${NC}"
|
||||
echo -n " Connectivity... "
|
||||
RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
|
||||
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
|
||||
VERSION=$(echo "$RESP" | jq -r '.version')
|
||||
echo -e " ${GREEN}OK${NC} (aw-server $VERSION)"
|
||||
else
|
||||
echo -e " ${RED}FAILED${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n " CORS... "
|
||||
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
|
||||
if [ "$CORS_RESP" = "200" ]; then
|
||||
echo -e "${GREEN}OK${NC}"
|
||||
else
|
||||
echo -e "${RED}FAIL${NC} (HTTP $CORS_RESP)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Проверка бакетов
|
||||
echo -e "${CYAN}--- 2. Data Buckets ---${NC}"
|
||||
printf " %-42s %-8s %-20s %s\n" "BUCKET" "EVENTS" "LAST EVENT" "STATUS"
|
||||
printf " %-42s %-8s %-20s %s\n" "------------------------------------------" "--------" "--------------------" "------"
|
||||
|
||||
BUCKETS=(
|
||||
"aw-watcher-afk|AFK watcher"
|
||||
"aw-watcher-window|Window watcher"
|
||||
"aw-worktime-sessions|Worktime sessions"
|
||||
"aw-session-events|Session events"
|
||||
"aw-dlp-endpoint-signals|DLP signals"
|
||||
"aw-dlp-incidents|DLP incidents"
|
||||
"aw-dlp-review|DLP review"
|
||||
"aw-dlp-rules|DLP rules"
|
||||
)
|
||||
|
||||
for entry in "${BUCKETS[@]}"; do
|
||||
bucket="${entry%%|*}"
|
||||
label="${entry##*|}"
|
||||
bucket_full="${bucket}_${HOSTNAME_FILTER}"
|
||||
|
||||
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
|
||||
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
|
||||
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
|
||||
|
||||
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
|
||||
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
|
||||
if [ "$EVENT_EPOCH" -gt 0 ]; then
|
||||
AGE_SEC=$((NOW - EVENT_EPOCH))
|
||||
if [ $AGE_SEC -lt 3600 ]; then
|
||||
AGE="$((AGE_SEC / 60))m"
|
||||
STATUS="${GREEN}FRESH${NC}"
|
||||
elif [ $AGE_SEC -lt 86400 ]; then
|
||||
AGE="$((AGE_SEC / 3600))h"
|
||||
STATUS="${YELLOW}STALE${NC}"
|
||||
else
|
||||
AGE="$((AGE_SEC / 86400))d"
|
||||
STATUS="${RED}DEAD${NC}"
|
||||
fi
|
||||
else
|
||||
AGE="?"
|
||||
STATUS="${RED}?${NC}"
|
||||
fi
|
||||
else
|
||||
AGE="none"
|
||||
LAST_ID="0"
|
||||
STATUS="${RED}EMPTY${NC}"
|
||||
fi
|
||||
|
||||
printf " %-42s %-8s %-20s %b\n" "$label" "$LAST_ID" "$AGE" "$STATUS"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# 3. Проверка RDP-хоста
|
||||
echo -e "${CYAN}--- 3. RDP Host ($RDP_HOST) ---${NC}"
|
||||
|
||||
# Проверка WinRM
|
||||
echo -n " WinRM (5985)... "
|
||||
if timeout 5 bash -c "echo > /dev/tcp/$RDP_HOST/5985" 2>&1; then
|
||||
echo -e "${GREEN}OK${NC}"
|
||||
else
|
||||
echo -e "${RED}UNREACHABLE${NC}"
|
||||
fi
|
||||
|
||||
# Проверка SSH (для справки)
|
||||
echo -n " SSH (22)... "
|
||||
if timeout 5 bash -c "echo > /dev/tcp/$RDP_HOST/22" 2>&1; then
|
||||
echo -e "${GREEN}OK${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}CLOSED${NC} (normal for Windows)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Сводка
|
||||
echo -e "${CYAN}--- 4. Summary ---${NC}"
|
||||
FRESH_COUNT=0
|
||||
STALE_COUNT=0
|
||||
DEAD_COUNT=0
|
||||
|
||||
for entry in "${BUCKETS[@]}"; do
|
||||
bucket="${entry%%|*}"
|
||||
bucket_full="${bucket}_${HOSTNAME_FILTER}"
|
||||
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
|
||||
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
|
||||
|
||||
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
|
||||
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
|
||||
if [ "$EVENT_EPOCH" -gt 0 ]; then
|
||||
AGE_SEC=$((NOW - EVENT_EPOCH))
|
||||
if [ $AGE_SEC -lt 3600 ]; then
|
||||
FRESH_COUNT=$((FRESH_COUNT + 1))
|
||||
elif [ $AGE_SEC -lt 86400 ]; then
|
||||
STALE_COUNT=$((STALE_COUNT + 1))
|
||||
else
|
||||
DEAD_COUNT=$((DEAD_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
else
|
||||
DEAD_COUNT=$((DEAD_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " FRESH: ${GREEN}$FRESH_COUNT${NC}"
|
||||
echo -e " STALE: ${YELLOW}$STALE_COUNT${NC}"
|
||||
echo -e " DEAD: ${RED}$DEAD_COUNT${NC}"
|
||||
|
||||
if [ $DEAD_COUNT -gt 0 ] || [ $STALE_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e " ${RED}WARNING:${NC} Some collectors may need restart on RDP host"
|
||||
echo -e " Run: ${CYAN}ansible -i ansible/inventory.ini rdp-prod -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}=== Check Complete ===${NC}"
|
||||
echo " Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
@@ -0,0 +1,303 @@
|
||||
# ActivityWatch-Russian Architecture Diagram
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Windows Clients"
|
||||
A[ActivityWatch Watchers]
|
||||
B[DLP Endpoint Collector]
|
||||
C[Browser Domains Collector]
|
||||
D[Email Outbound Collector]
|
||||
E[Worktime Session Collector]
|
||||
end
|
||||
|
||||
subgraph "Linux Server"
|
||||
F[ActivityWatch Server]
|
||||
G[PostgreSQL Database]
|
||||
H[Aggregation Scripts]
|
||||
end
|
||||
|
||||
subgraph "Monitoring & Integration"
|
||||
I[pfSense Firewall]
|
||||
J[Prometheus Exporter]
|
||||
end
|
||||
|
||||
subgraph "Visualization"
|
||||
K[Grafana]
|
||||
L[Prometheus]
|
||||
M[ActivityWatch WebUI]
|
||||
end
|
||||
|
||||
A -->|HTTP API| F
|
||||
B -->|Events| F
|
||||
C -->|Events| F
|
||||
D -->|Events| F
|
||||
E -->|Events| F
|
||||
|
||||
F -->|Store| G
|
||||
F -->|API| M
|
||||
|
||||
I -->|Logs| F
|
||||
H -->|Read/Write| G
|
||||
H -->|Read| F
|
||||
|
||||
J -->|Metrics| L
|
||||
L -->|Scrape| J
|
||||
F -->|API| J
|
||||
|
||||
K -->|Query| L
|
||||
K -->|Query| G
|
||||
```
|
||||
|
||||
## Component Interactions
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
Windows Collectors → ActivityWatch Server → PostgreSQL Database
|
||||
↓
|
||||
Aggregation Scripts
|
||||
↓
|
||||
Grafana Dashboards
|
||||
```
|
||||
|
||||
### DLP Monitoring Flow
|
||||
|
||||
```
|
||||
User Activity (clipboard/print/USB)
|
||||
↓
|
||||
DLP Endpoint Collector (PowerShell)
|
||||
↓
|
||||
DLP Policy Evaluation
|
||||
↓
|
||||
ActivityWatch Events (via HTTP API)
|
||||
↓
|
||||
PostgreSQL Database
|
||||
↓
|
||||
Aggregation Scripts (Python)
|
||||
↓
|
||||
Grafana DLP Dashboard
|
||||
```
|
||||
|
||||
### Browser Monitoring Flow
|
||||
|
||||
```
|
||||
User Browser Activity
|
||||
↓
|
||||
Browser Domains Collector (PowerShell)
|
||||
↓
|
||||
Domain Extraction & Categorization
|
||||
↓
|
||||
DLP Rule Check
|
||||
↓
|
||||
ActivityWatch Events
|
||||
↓
|
||||
WebUI Domain Dashboard
|
||||
```
|
||||
|
||||
### Email Monitoring Flow
|
||||
|
||||
```
|
||||
Outlook / SMTP Activity
|
||||
↓
|
||||
Email Outbound Collector (PowerShell)
|
||||
↓
|
||||
Email Policy Evaluation
|
||||
↓
|
||||
ActivityWatch Events
|
||||
↓
|
||||
Grafana Email Dashboard
|
||||
```
|
||||
|
||||
### pfSense Integration Flow
|
||||
|
||||
```
|
||||
pfSense Firewall Logs
|
||||
↓
|
||||
pfSense Poller (Python)
|
||||
↓
|
||||
HTTP API Query
|
||||
↓
|
||||
ActivityWatch Events
|
||||
↓
|
||||
Network Activity Dashboard
|
||||
```
|
||||
|
||||
### Metrics Flow
|
||||
|
||||
```
|
||||
ActivityWatch Server
|
||||
↓
|
||||
Prometheus Exporter (Python)
|
||||
↓
|
||||
HTTP Metrics Endpoint (port 9398)
|
||||
↓
|
||||
Prometheus Scraping
|
||||
↓
|
||||
Grafana Dashboards
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### Windows Collectors
|
||||
|
||||
| Component | Language | Purpose | Output |
|
||||
|-----------|----------|---------|--------|
|
||||
| DLP Endpoint Collector | PowerShell | Monitor clipboard, print, USB | ActivityWatch events |
|
||||
| Browser Domains Collector | PowerShell | Track visited domains | ActivityWatch events |
|
||||
| Email Outbound Collector | PowerShell | Monitor sent emails | ActivityWatch events |
|
||||
| Worktime Session Collector | PowerShell | Track work sessions | ActivityWatch events |
|
||||
|
||||
### Server Components
|
||||
|
||||
| Component | Language | Purpose | Dependencies |
|
||||
|-----------|----------|---------|--------------|
|
||||
| ActivityWatch Server | Rust | Core monitoring platform | SQLite/PostgreSQL |
|
||||
| Aggregation Scripts | Python | Process DLP events | psycopg2, requests |
|
||||
| Prometheus Exporter | Python | Export metrics to Prometheus | prometheus_client |
|
||||
|
||||
### Integration Points
|
||||
|
||||
| Integration | Protocol | Purpose |
|
||||
|-------------|----------|---------|
|
||||
| pfSense → AW | HTTP API | Firewall log collection |
|
||||
| AW → Grafana | PostgreSQL | Direct database access |
|
||||
| AW → Prometheus | HTTP /metrics | Metrics scraping |
|
||||
| Collectors → AW | HTTP /api/buckets | Event submission |
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
```
|
||||
Domain Controller
|
||||
↓ (GPO / Scheduled Tasks)
|
||||
Windows Workstations (user1, user2, ...)
|
||||
↓ (PowerShell Collectors)
|
||||
ActivityWatch Server (Linux)
|
||||
↓
|
||||
PostgreSQL Database
|
||||
↓
|
||||
Grafana + Prometheus Stack
|
||||
```
|
||||
|
||||
## File System Structure
|
||||
|
||||
```
|
||||
ActivityWatch-Russian/
|
||||
├── windows/ # Windows collectors
|
||||
│ ├── dlp-endpoint-signals-collector.ps1
|
||||
│ ├── browser-domains-native-collector.ps1
|
||||
│ ├── email-outbound-collector.ps1
|
||||
│ ├── worktime-session-collector.ps1
|
||||
│ ├── deploy-domain-users.ps1 # Deployment script
|
||||
│ └── ActivityWatch.Windows.Common.psm1
|
||||
├── scripts/ # Server scripts
|
||||
│ ├── aggregate_dlp_events.py
|
||||
│ └── merge_aw_server_dbs.py
|
||||
├── aw-server/ # WebUI patches
|
||||
│ ├── aw-ru-patch.js
|
||||
│ └── aw-sw-cleanup.js
|
||||
├── pfsense/ # pfSense integration
|
||||
│ └── pfsense-aw-poller.py
|
||||
├── grafana-1c/ # Monitoring stack
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── prometheus/
|
||||
│ ├── grafana/
|
||||
│ └── sql-exporter/
|
||||
│ └── collectors/
|
||||
│ └── aw_activitywatch.py
|
||||
└── ansible/ # Automation
|
||||
├── playbooks/
|
||||
└── group_vars/
|
||||
```
|
||||
|
||||
## Network Ports
|
||||
|
||||
| Service | Port | Protocol | Purpose |
|
||||
|---------|------|----------|---------|
|
||||
| ActivityWatch Server | 5600 | HTTP | WebUI & API |
|
||||
| ActivityWatch Server | 5666 | HTTP | WebSocket |
|
||||
| Prometheus | 9090 | HTTP | Metrics UI |
|
||||
| Prometheus Exporter | 9398 | HTTP | AW metrics endpoint |
|
||||
| Grafana | 3000 | HTTP | Dashboards |
|
||||
| PostgreSQL | 5432 | TCP | Database |
|
||||
|
||||
## Data Models
|
||||
|
||||
### ActivityWatch Event Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"duration": 60.0,
|
||||
"data": {
|
||||
"title": "Window Title",
|
||||
"url": "https://example.com",
|
||||
"app": "chrome.exe",
|
||||
"type": "activity"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Incident Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "clipboard",
|
||||
"rule": "credit_card_pattern",
|
||||
"severity": "high",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01",
|
||||
"screenshot": "path/to/screenshot.png"
|
||||
}
|
||||
```
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
### Windows Dependencies
|
||||
- PowerShell 5.1+
|
||||
- .NET Framework 4.8
|
||||
- ActivityWatch Windows binaries
|
||||
|
||||
### Linux Dependencies
|
||||
- Python 3.8+
|
||||
- PostgreSQL 12+
|
||||
- Docker & Docker Compose
|
||||
- Rust (for AW server compilation)
|
||||
|
||||
### Python Dependencies
|
||||
```txt
|
||||
psycopg2-binary
|
||||
requests
|
||||
prometheus_client
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Data Flow Security
|
||||
- Collectors → Server: HTTP (can be upgraded to HTTPS)
|
||||
- Server → Database: Local connection or SSL
|
||||
- pfSense → Server: HTTP over VPN
|
||||
- Prometheus → Exporter: HTTP internal network
|
||||
|
||||
### Access Control
|
||||
- Windows collectors run as user context
|
||||
- Server runs as dedicated service user
|
||||
- Database access restricted to specific users
|
||||
- Grafana authenticated via LDAP/Local users
|
||||
|
||||
## Monitoring & Alerting
|
||||
|
||||
### Metrics Collected
|
||||
- Events per bucket
|
||||
- Active hosts count
|
||||
- Collector heartbeat status
|
||||
- DLP incident rate
|
||||
- Database query performance
|
||||
|
||||
### Alerting Rules
|
||||
- Collector offline > 30 minutes
|
||||
- High DLP incident rate
|
||||
- Database connection failures
|
||||
- Disk space < 20%
|
||||
@@ -0,0 +1,278 @@
|
||||
# ActivityWatch-Russian - Простая архитектура
|
||||
|
||||
## Общая схема (сверху вниз)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ПОЛЬЗОВАТЕЛИ │
|
||||
│ (Windows рабочие станции) │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Коллекторы (PowerShell)
|
||||
│
|
||||
┌──────────────────────────▼──────────────────────────────────────┐
|
||||
│ Windows Collectors (на каждой машине) │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ • DLP Endpoint Collector - буфер, печать, USB │
|
||||
│ • Browser Domains Collector - посещаемые сайты │
|
||||
│ • Email Outbound Collector - исходящая почта │
|
||||
│ • Worktime Session Collector - рабочее время │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ HTTP API (отправка событий)
|
||||
│
|
||||
┌──────────────────────────▼──────────────────────────────────────┐
|
||||
│ ActivityWatch Server (Linux) │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ • Принимает события от коллекторов │
|
||||
│ • Хранит в базе данных │
|
||||
│ • Предоставляет WebUI и API │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Хранение данных
|
||||
│
|
||||
┌──────────────────────────▼──────────────────────────────────────┐
|
||||
│ PostgreSQL Database │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ • События активности │
|
||||
│ • DLP инциденты │
|
||||
│ • Метаданные хостов и пользователей │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Чтение/Обработка
|
||||
│
|
||||
┌──────────────────────────▼──────────────────────────────────────┐
|
||||
│ Обработка и Агрегация │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ • aggregate_dlp_events.py - агрегация DLP событий │
|
||||
│ • pfSense Poller - логи firewall │
|
||||
│ • Prometheus Exporter - метрики для мониторинга │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Визуализация
|
||||
│
|
||||
┌──────────────────────────▼──────────────────────────────────────┐
|
||||
│ Grafana + Prometheus Stack │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ • Prometheus - сбор метрик │
|
||||
│ • Grafana - дашборды и визуализация │
|
||||
│ • SQL Exporter - прямой доступ к PostgreSQL │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Потоки данных (по направлениям)
|
||||
|
||||
### Поток 1: DLP мониторинг
|
||||
```
|
||||
Пользователь → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
|
||||
```
|
||||
|
||||
### Поток 2: Мониторинг браузеров
|
||||
```
|
||||
Браузер → Browser Collector → Категоризация → AW Server → WebUI Dashboard
|
||||
```
|
||||
|
||||
### Поток 3: Мониторинг почты
|
||||
```
|
||||
Outlook/SMTP → Email Collector → Проверка правил → AW Server → Grafana
|
||||
```
|
||||
|
||||
### Поток 4: pfFirewall логи
|
||||
```
|
||||
pfSense → Poller (Python) → HTTP API → AW Server → Grafana
|
||||
```
|
||||
|
||||
### Поток 5: Метрики
|
||||
```
|
||||
AW Server → Exporter (Python) → /metrics → Prometheus → Grafana
|
||||
```
|
||||
|
||||
## Компоненты по уровням
|
||||
|
||||
### Уровень 1: Сбор данных (Windows)
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Windows Collectors (PowerShell) │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ • dlp-endpoint-signals-collector│ │
|
||||
│ │ • browser-domains-collector │ │
|
||||
│ │ • email-outbound-collector │ │
|
||||
│ │ • worktime-session-collector │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Уровень 2: Хранение и обработка (Linux Server)
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ ActivityWatch Server + PostgreSQL │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ • aw-server (Rust) │ │
|
||||
│ │ • PostgreSQL Database │ │
|
||||
│ │ • WebUI (с RU патчами) │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Уровень 3: Интеграции и обработка
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Интеграции и Скрипты (Python) │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ • aggregate_dlp_events.py │ │
|
||||
│ │ • pfsense-aw-poller.py │ │
|
||||
│ │ • aw_activitywatch_exporter │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Уровень 4: Визуализация и мониторинг
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Grafana + Prometheus (Docker) │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ • Prometheus (порт 9090) │ │
|
||||
│ │ • Grafana (порт 3000) │ │
|
||||
│ │ • SQL Exporter (порт 9398) │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Развертывание
|
||||
|
||||
### На Windows рабочих станциях
|
||||
```
|
||||
1. Установка ActivityWatch (через InnoSetup installer)
|
||||
2. Развертывание коллекторов (deploy-domain-users.ps1)
|
||||
3. Настройка scheduled tasks
|
||||
4. Конфигурация DLP политик
|
||||
```
|
||||
|
||||
### На Linux сервере
|
||||
```
|
||||
1. Установка ActivityWatch Server
|
||||
2. Настройка PostgreSQL
|
||||
3. Применение RU патчей к WebUI
|
||||
4. Запуск скриптов агрегации
|
||||
5. Запуск Prometheus Exporter
|
||||
```
|
||||
|
||||
### Мониторинг стек
|
||||
```
|
||||
1. Docker Compose развертывание
|
||||
2. Настройка Prometheus scrape config
|
||||
3. Импорт Grafana дашбордов
|
||||
4. Настройка алертов
|
||||
```
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
```
|
||||
ActivityWatch-Russian/
|
||||
├── windows/ # Windows коллекторы
|
||||
│ ├── dlp-endpoint-signals-collector.ps1
|
||||
│ ├── browser-domains-native-collector.ps1
|
||||
│ ├── email-outbound-collector.ps1
|
||||
│ └── deploy-domain-users.ps1
|
||||
│
|
||||
├── scripts/ # Серверные скрипты
|
||||
│ └── aggregate_dlp_events.py
|
||||
│
|
||||
├── aw-server/ # Патчи WebUI
|
||||
│ └── aw-ru-patch.js
|
||||
│
|
||||
├── pfsense/ # Интеграция pfSense
|
||||
│ └── pfsense-aw-poller.py
|
||||
│
|
||||
└── grafana-1c/ # Мониторинг стек
|
||||
├── docker-compose.yml
|
||||
├── prometheus/prometheus.yml
|
||||
└── grafana/dashboards/
|
||||
```
|
||||
|
||||
## Порты
|
||||
|
||||
| Компонент | Порт | Протокол |
|
||||
|-----------|------|----------|
|
||||
| ActivityWatch WebUI | 5600 | HTTP |
|
||||
| ActivityWatch API | 5666 | WebSocket |
|
||||
| Prometheus | 9090 | HTTP |
|
||||
| Prometheus Exporter | 9398 | HTTP |
|
||||
| Grafana | 3000 | HTTP |
|
||||
| PostgreSQL | 5432 | TCP |
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Запуск мониторинг стека
|
||||
```bash
|
||||
cd grafana-1c
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Развертывание на Windows
|
||||
```powersShell
|
||||
.\windows\deploy-domain-users.ps1
|
||||
```
|
||||
|
||||
### Агрегация DLP событий
|
||||
```bash
|
||||
python3 scripts/aggregate_dlp_events.py
|
||||
```
|
||||
|
||||
## Связи между компонентами
|
||||
|
||||
```
|
||||
Windows Collectors
|
||||
│
|
||||
├─► ActivityWatch Server (HTTP API)
|
||||
│ │
|
||||
│ ├─► PostgreSQL (хранение)
|
||||
│ │
|
||||
│ ├─► WebUI (RU патчи)
|
||||
│ │
|
||||
│ └─► Prometheus Exporter (метрики)
|
||||
│ │
|
||||
│ └─► Prometheus
|
||||
│ │
|
||||
│ └─► Grafana
|
||||
│
|
||||
pfSense Firewall
|
||||
│
|
||||
└─► pfSense Poller
|
||||
│
|
||||
└─► ActivityWatch Server
|
||||
│
|
||||
└─► PostgreSQL
|
||||
```
|
||||
|
||||
## Основные сценарии
|
||||
|
||||
### Сценарий 1: Пользователь копирует конфиденциальные данные
|
||||
```
|
||||
1. Пользователь копирует текст в буфер обмена
|
||||
2. DLP Collector перехватывает событие
|
||||
3. Проверка по DLP правилам
|
||||
4. При совпадении → скриншот + запись инцидента
|
||||
5. Отправка в ActivityWatch Server
|
||||
6. Сохранение в PostgreSQL
|
||||
7. Отображение в Grafana DLP Dashboard
|
||||
```
|
||||
|
||||
### Сценарий 2: Пользователь посещает запрещенный сайт
|
||||
```
|
||||
1. Пользователь открывает сайт в браузере
|
||||
2. Browser Collector определяет домен
|
||||
3. Проверка по спискам и DLP правилам
|
||||
4. Категоризация сайта
|
||||
5. Отправка события в ActivityWatch
|
||||
6. Отображение в WebUI Dashboard
|
||||
```
|
||||
|
||||
### Сценарий 3: Мониторинг метрик
|
||||
```
|
||||
1. Prometheus Exporter опрашивает AW API
|
||||
2. Сбор метрик (события, хосты, коллекторы)
|
||||
3. Экспорт в формате Prometheus
|
||||
4. Prometheus scrapes endpoint /metrics
|
||||
5. Grafana строит графики
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
# Onboarding по кодовой базе AWatch-rus
|
||||
|
||||
Этот документ — быстрый вход для новичка: **что лежит где**, **как всё связано** и **что читать дальше**.
|
||||
|
||||
## 1) Что это за репозиторий
|
||||
|
||||
`AWatch-rus` — это не один сервис, а **инфраструктурный набор** для развёртывания и сопровождения ActivityWatch в прод-подобной среде:
|
||||
|
||||
- Proxmox/LXC-подготовка,
|
||||
- установка и настройка AW Server,
|
||||
- русификация Web UI,
|
||||
- автоматизация через Ansible,
|
||||
- клиентский rollout для Windows/Linux,
|
||||
- дополнительная телеметрия (в т.ч. pfSense poller и DLP-сигналы),
|
||||
- эксплуатационные runbook/operations документы.
|
||||
|
||||
Идея: чтобы развёртывание было **повторяемым**, а не «ручной магией в один вечер».
|
||||
|
||||
## 2) Карта проекта (по папкам)
|
||||
|
||||
### `docs/`
|
||||
Главный источник истины по процессам.
|
||||
|
||||
- `preparation.md` — входные данные, prerequisites, что нужно до старта.
|
||||
- `deployment.md` — базовый серверный деплой.
|
||||
- `runbook.md` — быстрые операционные действия и проверки.
|
||||
- `operations.md` — сопровождение, бэкапы, rollback.
|
||||
- `windows/*` — отдельная ветка документации по Windows-оркестрации.
|
||||
- `1C_GRAFANA_DEPLOYMENT_RU.md`, `pfsense.md`, `linux-client.md` — специализированные подсистемы.
|
||||
|
||||
### `proxmox/`
|
||||
Скрипты ранней инфраструктурной фазы:
|
||||
|
||||
- `create-ct.sh` — создание/подготовка контейнера,
|
||||
- `push-aw-artifacts.sh` — доставка артефактов и конфигов.
|
||||
|
||||
### `aw-server/`
|
||||
Серверная «сердцевина»:
|
||||
|
||||
- `install_aw_server.sh` — установка AW Server,
|
||||
- `apply_webui_ru_patch.sh` + `aw-ru-patch.js` — русификация UI,
|
||||
- `activitywatch-server.service` — unit для systemd,
|
||||
- `aw-server.env.example` — шаблон переменных окружения,
|
||||
- `settings/*.json` — конфигурация представлений/классов.
|
||||
|
||||
### `ansible/`
|
||||
Идемпотентная автоматизация (вместо ручных команд):
|
||||
|
||||
- playbook'и для серверного деплоя,
|
||||
- сценарии provisioning + deploy,
|
||||
- `group_vars/*.example.yml` и `inventory.example.ini` как шаблоны входных данных.
|
||||
|
||||
### `windows/`
|
||||
PowerShell toolkit для клиентской стороны:
|
||||
|
||||
- `deploy-ensemble.ps1` — оркестратор,
|
||||
- `deploy-single-user.ps1`, `deploy-domain-users.ps1` — сценарии установки,
|
||||
- `validate-deployment.ps1` — post-check,
|
||||
- `ActivityWatch.Windows.Common.psm1` — общая библиотека функций,
|
||||
- скрипты DLP/browser telemetry.
|
||||
|
||||
### `pfsense/`
|
||||
Отдельный poller для pfSense API + systemd unit.
|
||||
|
||||
### `grafana-1c/`
|
||||
Набор для SQL exporter + Prometheus + Grafana дашбордов по 1C метрикам.
|
||||
|
||||
### `scripts/`
|
||||
Утилиты и quality gates:
|
||||
|
||||
- `quality-gate.sh` — базовый preflight,
|
||||
- инсталляторы Linux-клиента и console/ssh logger режимов.
|
||||
|
||||
### `secrets/`
|
||||
Только шаблоны. Реальные секреты в репозиторий не кладутся.
|
||||
|
||||
## 3) Как компоненты связаны в потоке
|
||||
|
||||
Типовой pipeline:
|
||||
|
||||
1. Подготовка параметров (`docs/preparation.md`, `secrets/*.example`).
|
||||
2. Provisioning контейнера в Proxmox (`proxmox/`).
|
||||
3. Установка/настройка AW Server (`aw-server/`).
|
||||
4. Включение автозапуска и проверка (`systemd` + `docs/runbook.md`).
|
||||
5. Rollout клиентов (обычно `windows/`, иногда `scripts/install_aw_linux_client.sh`).
|
||||
6. Эксплуатация и изменения через `docs/operations.md`.
|
||||
7. При необходимости — расширение мониторинга (`grafana-1c/`, `pfsense/`).
|
||||
|
||||
## 4) Что важно понять в первую очередь
|
||||
|
||||
1. **Репозиторий документ-ориентированный**: сначала читаешь `docs/`, потом запускаешь скрипты.
|
||||
2. **Шаблоны `.example` — обязательная точка входа**: не редактируй скрипты вместо заполнения переменных.
|
||||
3. **Есть два режима работы**:
|
||||
- ручной/полуручной (bash + runbook),
|
||||
- автоматизированный (Ansible).
|
||||
4. **Windows часть — полноценный под-проект** с собственными deploy/validate практиками.
|
||||
5. **Безопасность**: никаких секретов в git; rollback и backup — не опция, а стандарт процесса.
|
||||
|
||||
## 5) Рекомендованный порядок изучения (первые 2–3 часа)
|
||||
|
||||
1. `README.md` — получить общую картину.
|
||||
2. `docs/preparation.md` — понять входные параметры.
|
||||
3. `docs/deployment.md` — увидеть «сквозной» серверный сценарий.
|
||||
4. `docs/runbook.md` и `docs/operations.md` — как жить с системой после деплоя.
|
||||
5. `aw-server/install_aw_server.sh` и `aw-server/activitywatch-server.service` — как реально стартует сервис.
|
||||
6. `windows/deploy-ensemble.ps1` + `windows/validate-deployment.ps1` — клиентская фаза.
|
||||
7. `ansible/README.md` и ключевые playbook'и — переход к промышленной автоматизации.
|
||||
|
||||
## 6) Практические подсказки для первого вклада
|
||||
|
||||
- Начни с правок документации или `.example`-шаблонов — это самый безопасный вход.
|
||||
- Перед изменениями в скриптах сравни, нет ли уже Ansible-аналога (лучше поддерживать один «официальный» путь).
|
||||
- Любая новая переменная должна быть отражена:
|
||||
1) в `.example` файле,
|
||||
2) в docs,
|
||||
3) в проверках/валидации (если применимо).
|
||||
- Для Windows-скриптов используй общие функции из `ActivityWatch.Windows.Common.psm1`, чтобы не дублировать логику.
|
||||
|
||||
## 7) Куда смотреть дальше (углубление)
|
||||
|
||||
- Если интересует эксплуатация и инциденты: `docs/runbook.md`, `docs/operations.md`, `docs/windows/troubleshooting.md`.
|
||||
- Если интересует автодеплой: `ansible/provision_proxmox_ct_and_deploy_aw.yml` и `ansible/tasks/`.
|
||||
- Если интересует наблюдаемость: `grafana-1c/` + `pfsense/` + `prometheus`/`alerts` конфиги.
|
||||
- Если интересует hardening и DLP: `windows/hardening-recovery.ps1`, `docs/dlp-gap-analysis.md`.
|
||||
|
||||
---
|
||||
|
||||
Если ты новичок в проекте, практичный старт: разверни тестовый стенд по `docs/deployment.md`, затем прогоняй валидации из `docs/runbook.md` и `windows/validate-deployment.ps1`.
|
||||
@@ -0,0 +1,918 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ActivityWatch-Russian - Architecture Map</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.legend {
|
||||
background: rgba(255,255,255,0.95);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.legend h2 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.legend-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
|
||||
.architecture-map {
|
||||
background: rgba(255,255,255,0.95);
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
min-height: 800px;
|
||||
}
|
||||
|
||||
.layer {
|
||||
margin-bottom: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layer-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.components-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.component {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
|
||||
.component:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.component.selected {
|
||||
border-color: #667eea;
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.component.windows-client {
|
||||
border-top: 4px solid #ff6b6b;
|
||||
}
|
||||
|
||||
.component.linux-server {
|
||||
border-top: 4px solid #4ecdc4;
|
||||
}
|
||||
|
||||
.component.integration {
|
||||
border-top: 4px solid #ffe66d;
|
||||
}
|
||||
|
||||
.component.monitoring {
|
||||
border-top: 4px solid #95e1d3;
|
||||
}
|
||||
|
||||
.component.external {
|
||||
border-top: 4px solid #dda0dd;
|
||||
}
|
||||
|
||||
.component h3 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.component-description {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.component-tech {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tech-badge {
|
||||
background: #f0f0f0;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.connections {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.connections.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.connections h4 {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.connection-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
margin-bottom: 5px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border-left: 3px solid #667eea;
|
||||
}
|
||||
|
||||
.connection-protocol {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.connection-direction {
|
||||
color: #999;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
width: 350px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.info-panel.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info-panel h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.info-panel-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.info-section h4 {
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.info-section p, .info-section ul {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-section ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.flow-diagram {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.flow-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
color: #667eea;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.component.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.components-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
width: 90%;
|
||||
right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>ActivityWatch-Russian - Architecture Map</h1>
|
||||
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" placeholder="🔍 Поиск компонентов...">
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<h2>Легенда</h2>
|
||||
<div class="legend-items">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #ff6b6b;"></div>
|
||||
<span>Windows Clients</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #4ecdc4;"></div>
|
||||
<span>Linux Server</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #ffe66d;"></div>
|
||||
<span>Integration Layer</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #95e1d3;"></div>
|
||||
<span>Monitoring Stack</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #dda0dd;"></div>
|
||||
<span>External Systems</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="architecture-map">
|
||||
<!-- Windows Clients Layer -->
|
||||
<div class="layer">
|
||||
<div class="layer-title">🖥️ Windows Clients - Сбор данных</div>
|
||||
<div class="components-grid">
|
||||
<div class="component windows-client" data-id="dlp-endpoint" data-search="dlp endpoint collector clipboard print usb">
|
||||
<h3>DLP Endpoint Collector</h3>
|
||||
<p class="component-description">Мониторинг clipboard, печати и USB для обнаружения утечек данных</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">PowerShell</span>
|
||||
<span class="tech-badge">Windows API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component windows-client" data-id="browser-domains" data-search="browser domains collector web monitoring">
|
||||
<h3>Browser Domains Collector</h3>
|
||||
<p class="component-description">Отслеживание посещаемых доменов с категоризацией сайтов</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">PowerShell</span>
|
||||
<span class="tech-badge">Window API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component windows-client" data-id="email-outbound" data-search="email outbound collector outlook smtp">
|
||||
<h3>Email Outbound Collector</h3>
|
||||
<p class="component-description">Мониторинг исходящей почты через Outlook и SMTP</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">PowerShell</span>
|
||||
<span class="tech-badge">Outlook API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component windows-client" data-id="worktime-session" data-search="worktime session collector time tracking">
|
||||
<h3>Worktime Session Collector</h3>
|
||||
<p class="component-description">Отслеживание рабочих сессий и учет времени</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">PowerShell</span>
|
||||
<span class="tech-badge">Windows API</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Linux Server Layer -->
|
||||
<div class="layer">
|
||||
<div class="layer-title">🐧 Linux Server - Хранение и обработка</div>
|
||||
<div class="components-grid">
|
||||
<div class="component linux-server" data-id="aw-server" data-search="activitywatch server rust api">
|
||||
<h3>ActivityWatch Server</h3>
|
||||
<p class="component-description">Основной сервер для приема, хранения и обработки событий</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Rust</span>
|
||||
<span class="tech-badge">HTTP API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component linux-server" data-id="postgresql" data-search="postgresql database storage">
|
||||
<h3>PostgreSQL Database</h3>
|
||||
<p class="component-description">Основное хранилище событий, метаданных и конфигураций</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">PostgreSQL</span>
|
||||
<span class="tech-badge">SQL</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component linux-server" data-id="webui" data-search="webui russian localization patches">
|
||||
<h3>WebUI with RU Patches</h3>
|
||||
<p class="component-description">Веб-интерфейс с русской локализацией и DLP компонентами</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">JavaScript</span>
|
||||
<span class="tech-badge">Vue.js</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integration Layer -->
|
||||
<div class="layer">
|
||||
<div class="layer-title">🔗 Integration Layer - Интеграции</div>
|
||||
<div class="components-grid">
|
||||
<div class="component integration" data-id="pfsense-poller" data-search="pfsense poller firewall logs">
|
||||
<h3>pfSense Poller</h3>
|
||||
<p class="component-description">Сбор логов с pfSense firewall и отправка в ActivityWatch</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Python</span>
|
||||
<span class="tech-badge">HTTP API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component integration" data-id="dlp-aggregation" data-search="dlp aggregation scripts postgresql">
|
||||
<h3>DLP Aggregation Scripts</h3>
|
||||
<p class="component-description">Агрегация DLP событий из ActivityWatch в PostgreSQL</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Python</span>
|
||||
<span class="tech-badge">psycopg2</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component integration" data-id="prometheus-exporter" data-search="prometheus exporter metrics">
|
||||
<h3>Prometheus Exporter</h3>
|
||||
<p class="component-description">Экспорт метрик ActivityWatch в формате Prometheus</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Python</span>
|
||||
<span class="tech-badge">Prometheus</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monitoring Stack -->
|
||||
<div class="layer">
|
||||
<div class="layer-title">📊 Monitoring Stack - Мониторинг</div>
|
||||
<div class="components-grid">
|
||||
<div class="component monitoring" data-id="prometheus" data-search="prometheus metrics monitoring">
|
||||
<h3>Prometheus</h3>
|
||||
<p class="component-description">Сбор и хранение метрик для мониторинга системы</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Prometheus</span>
|
||||
<span class="tech-badge">TSDB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component monitoring" data-id="grafana" data-search="grafana dashboards visualization">
|
||||
<h3>Grafana</h3>
|
||||
<p class="component-description">Визуализация метрик и построение дашбордов</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Grafana</span>
|
||||
<span class="tech-badge">Dashboards</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component monitoring" data-id="sql-exporter" data-search="sql exporter postgresql grafana">
|
||||
<h3>SQL Exporter</h3>
|
||||
<p class="component-description">Прямые SQL запросы к PostgreSQL для Grafana</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">Python</span>
|
||||
<span class="tech-badge">SQL</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- External Systems -->
|
||||
<div class="layer">
|
||||
<div class="layer-title">🌐 External Systems - Внешние системы</div>
|
||||
<div class="components-grid">
|
||||
<div class="component external" data-id="pfsense" data-search="pfsense firewall network">
|
||||
<h3>pfSense Firewall</h3>
|
||||
<p class="component-description">Брандмауэр для мониторинга сетевой активности</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">pfSense</span>
|
||||
<span class="tech-badge">Firewall</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component external" data-id="domain-controller" data-search="domain controller active directory gpo">
|
||||
<h3>Domain Controller</h3>
|
||||
<p class="component-description">Развертывание коллекторов через GPO и управление пользователями</p>
|
||||
<div class="component-tech">
|
||||
<span class="tech-badge">AD</span>
|
||||
<span class="tech-badge">GPO</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-panel" id="infoPanel">
|
||||
<button class="info-panel-close" onclick="closeInfoPanel()">×</button>
|
||||
<h3 id="infoPanelTitle">Component Info</h3>
|
||||
<div id="infoPanelContent"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Component data with connections
|
||||
const componentsData = {
|
||||
'dlp-endpoint': {
|
||||
title: 'DLP Endpoint Collector',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка DLP событий' }
|
||||
],
|
||||
flows: [
|
||||
'Пользователь копирует данные',
|
||||
'DLP Collector перехватывает событие',
|
||||
'Проверка по DLP правилам',
|
||||
'При нарушении → скриншот + инцидент',
|
||||
'Отправка в ActivityWatch Server'
|
||||
],
|
||||
ports: ['HTTP:5600'],
|
||||
details: 'Мониторинг clipboard, печати и USB устройств для обнаружения утечек конфиденциальных данных. Поддерживает блокировку нарушений и создание скриншотов.'
|
||||
},
|
||||
'browser-domains': {
|
||||
title: 'Browser Domains Collector',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка браузерных событий' }
|
||||
],
|
||||
flows: [
|
||||
'Пользователь открывает сайт',
|
||||
'Collector определяет домен',
|
||||
'Категоризация сайта',
|
||||
'Проверка DLP правил',
|
||||
'Отправка события в ActivityWatch'
|
||||
],
|
||||
ports: ['HTTP:5600'],
|
||||
details: 'Отслеживание посещаемых веб-сайтов с автоматической категоризацией и проверкой по DLP правилам для запрещенных доменов.'
|
||||
},
|
||||
'email-outbound': {
|
||||
title: 'Email Outbound Collector',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка email событий' }
|
||||
],
|
||||
flows: [
|
||||
'Пользователь отправляет email',
|
||||
'Collector перехватывает событие',
|
||||
'Проверка по email правилам',
|
||||
'При нарушении → блокировка',
|
||||
'Логирование в ActivityWatch'
|
||||
],
|
||||
ports: ['HTTP:5600'],
|
||||
details: 'Мониторинг исходящей почты через Outlook и SMTP соединения для предотвращения утечек через email.'
|
||||
},
|
||||
'worktime-session': {
|
||||
title: 'Worktime Session Collector',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка сессий' }
|
||||
],
|
||||
flows: [
|
||||
'Определение начала работы',
|
||||
'Отслеживание активности',
|
||||
'Определение перерывов',
|
||||
'Агрегация по дням',
|
||||
'Отправка в ActivityWatch'
|
||||
],
|
||||
ports: ['HTTP:5600'],
|
||||
details: 'Учет рабочего времени сотрудников с определением начала/конца сессий и перерывов.'
|
||||
},
|
||||
'aw-server': {
|
||||
title: 'ActivityWatch Server',
|
||||
connections: [
|
||||
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Хранение данных' },
|
||||
{ target: 'webui', protocol: 'HTTP', direction: '→', description: 'Отдача интерфейса' },
|
||||
{ target: 'prometheus-exporter', protocol: 'HTTP API', direction: '←', description: 'Метрики' }
|
||||
],
|
||||
flows: [
|
||||
'Прием событий от коллекторов',
|
||||
'Валидация и нормализация',
|
||||
'Сохранение в PostgreSQL',
|
||||
'Предоставление API',
|
||||
'Отдача WebUI'
|
||||
],
|
||||
ports: ['HTTP:5600', 'WebSocket:5666'],
|
||||
details: 'Основной сервер ActivityWatch на Rust, принимающий события от коллекторов и предоставляющий API и WebUI.'
|
||||
},
|
||||
'postgresql': {
|
||||
title: 'PostgreSQL Database',
|
||||
connections: [
|
||||
{ target: 'dlp-aggregation', protocol: 'TCP', direction: '↔', description: 'Агрегация событий' },
|
||||
{ target: 'grafana', protocol: 'TCP', direction: '→', description: 'Прямые запросы' },
|
||||
{ target: 'sql-exporter', protocol: 'TCP', direction: '→', description: 'SQL запросы' }
|
||||
],
|
||||
flows: [
|
||||
'Хранение событий активности',
|
||||
'Хранение DLP инцидентов',
|
||||
'Хранение метаданных',
|
||||
'Агрегация данных',
|
||||
'Аналитические запросы'
|
||||
],
|
||||
ports: ['TCP:5432'],
|
||||
details: 'Основная база данных для хранения всех событий, инцидентов и метаданных системы.'
|
||||
},
|
||||
'webui': {
|
||||
title: 'WebUI with RU Patches',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP', direction: '←', description: 'API запросы' }
|
||||
],
|
||||
flows: [
|
||||
'Загрузка WebUI',
|
||||
'Применение RU патчей',
|
||||
'Инъекция DLP компонентов',
|
||||
'Перевод интерфейса',
|
||||
'Отображение данных'
|
||||
],
|
||||
ports: ['HTTP:5600'],
|
||||
details: 'Веб-интерфейс с русской локализацией, DLP центром и компонентами для анализа активности.'
|
||||
},
|
||||
'pfsense-poller': {
|
||||
title: 'pfSense Poller',
|
||||
connections: [
|
||||
{ target: 'pfsense', protocol: 'HTTPS', direction: '←', description: 'Сбор логов' },
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка событий' }
|
||||
],
|
||||
flows: [
|
||||
'Подключение к pfSense API',
|
||||
'Сбор логов firewall',
|
||||
'Нормализация данных',
|
||||
'Отправка в ActivityWatch',
|
||||
'Периодический опрос'
|
||||
],
|
||||
ports: ['HTTPS:443'],
|
||||
details: 'Python скрипт для сбора логов с pfSense firewall и отправки сетевых событий в ActivityWatch.'
|
||||
},
|
||||
'dlp-aggregation': {
|
||||
title: 'DLP Aggregation Scripts',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Чтение событий' },
|
||||
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Запись агрегации' }
|
||||
],
|
||||
flows: [
|
||||
'Чтение событий из AW',
|
||||
'Парсинг и нормализация',
|
||||
'Агрегация по периодам',
|
||||
'Запись в PostgreSQL',
|
||||
'Генерация отчетов'
|
||||
],
|
||||
ports: ['TCP:5432'],
|
||||
details: 'Python скрипты для агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики.'
|
||||
},
|
||||
'prometheus-exporter': {
|
||||
title: 'Prometheus Exporter',
|
||||
connections: [
|
||||
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Сбор метрик' },
|
||||
{ target: 'prometheus', protocol: 'HTTP', direction: '→', description: 'Экспорт метрик' }
|
||||
],
|
||||
flows: [
|
||||
'Запрос к AW API',
|
||||
'Сбор метрик buckets',
|
||||
'Подсчет событий',
|
||||
'Форматирование Prometheus',
|
||||
'Отдача на /metrics'
|
||||
],
|
||||
ports: ['HTTP:9398'],
|
||||
details: 'HTTP сервер для экспорта метрик ActivityWatch в формате Prometheus для мониторинга.'
|
||||
},
|
||||
'prometheus': {
|
||||
title: 'Prometheus',
|
||||
connections: [
|
||||
{ target: 'prometheus-exporter', protocol: 'HTTP', direction: '←', description: 'Scraping' }
|
||||
],
|
||||
flows: [
|
||||
'Периодический scrape',
|
||||
'Сбор метрик',
|
||||
'Хранение в TSDB',
|
||||
'Evaluation правил',
|
||||
'Alerting'
|
||||
],
|
||||
ports: ['HTTP:9090'],
|
||||
details: 'Система мониторинга для сбора и хранения метрик с оценкой правил и алертингом.'
|
||||
},
|
||||
'grafana': {
|
||||
title: 'Grafana',
|
||||
connections: [
|
||||
{ target: 'prometheus', protocol: 'HTTP', direction: '←', description: 'Запросы метрик' },
|
||||
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' }
|
||||
],
|
||||
flows: [
|
||||
'Запрос к Prometheus',
|
||||
'Запрос к PostgreSQL',
|
||||
'Визуализация данных',
|
||||
'Построение графиков',
|
||||
'Дашборды'
|
||||
],
|
||||
ports: ['HTTP:3000'],
|
||||
details: 'Платформа визуализации для построения дашбордов на основе метрик Prometheus и данных PostgreSQL.'
|
||||
},
|
||||
'sql-exporter': {
|
||||
title: 'SQL Exporter',
|
||||
connections: [
|
||||
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' },
|
||||
{ target: 'grafana', protocol: 'HTTP', direction: '→', description: 'Данные для дашбордов' }
|
||||
],
|
||||
flows: [
|
||||
'SQL запросы к PostgreSQL',
|
||||
'Агрегация данных',
|
||||
'Форматирование',
|
||||
'Экспорт в Grafana',
|
||||
'Периодическое обновление'
|
||||
],
|
||||
ports: ['HTTP:9398'],
|
||||
details: 'Экспорт данных из PostgreSQL через SQL запросы для использования в Grafana дашбордах.'
|
||||
},
|
||||
'pfsense': {
|
||||
title: 'pfSense Firewall',
|
||||
connections: [
|
||||
{ target: 'pfsense-poller', protocol: 'HTTPS', direction: '→', description: 'API доступ' }
|
||||
],
|
||||
flows: [
|
||||
'Обработка трафика',
|
||||
'Логирование соединений',
|
||||
'Предоставление API',
|
||||
'Мониторинг безопасности'
|
||||
],
|
||||
ports: ['HTTPS:443', 'SSH:22'],
|
||||
details: 'Брандмауэр для мониторинга сетевой активности и защиты периметра.'
|
||||
},
|
||||
'domain-controller': {
|
||||
title: 'Domain Controller',
|
||||
connections: [
|
||||
{ target: 'dlp-endpoint', protocol: 'GPO', direction: '→', description: 'Развертывание' },
|
||||
{ target: 'browser-domains', protocol: 'GPO', direction: '→', description: 'Развертывание' },
|
||||
{ target: 'email-outbound', protocol: 'GPO', direction: '→', description: 'Развертывание' }
|
||||
],
|
||||
flows: [
|
||||
'Создание GPO',
|
||||
'Развертывание коллекторов',
|
||||
'Управление пользователями',
|
||||
'Мониторинг статуса'
|
||||
],
|
||||
ports: ['LDAP:389', 'SMB:445'],
|
||||
details: 'Active Directory для автоматического развертывания коллекторов через Group Policy Objects.'
|
||||
}
|
||||
};
|
||||
|
||||
// DOM elements
|
||||
const components = document.querySelectorAll('.component');
|
||||
const infoPanel = document.getElementById('infoPanel');
|
||||
const infoPanelTitle = document.getElementById('infoPanelTitle');
|
||||
const infoPanelContent = document.getElementById('infoPanelContent');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
|
||||
// Component click handler
|
||||
components.forEach(component => {
|
||||
component.addEventListener('click', () => {
|
||||
const componentId = component.dataset.id;
|
||||
const data = componentsData[componentId];
|
||||
|
||||
// Remove selected class from all components
|
||||
components.forEach(c => c.classList.remove('selected'));
|
||||
|
||||
// Add selected class to clicked component
|
||||
component.classList.add('selected');
|
||||
|
||||
// Show info panel
|
||||
showInfoPanel(componentId, data);
|
||||
|
||||
// Highlight connected components
|
||||
highlightConnections(componentId, data.connections);
|
||||
});
|
||||
});
|
||||
|
||||
function showInfoPanel(componentId, data) {
|
||||
infoPanelTitle.textContent = data.title;
|
||||
|
||||
let content = `
|
||||
<div class="info-section">
|
||||
<h4>📝 Описание</h4>
|
||||
<p>${data.details}</p>
|
||||
</div>
|
||||
<div class="info-section">
|
||||
<h4>🔗 Связи</h4>
|
||||
`;
|
||||
|
||||
data.connections.forEach(conn => {
|
||||
content += `
|
||||
<div class="connection-item">
|
||||
<span class="connection-protocol">${conn.protocol}</span>
|
||||
<span class="connection-direction">${conn.direction}</span>
|
||||
<span>${conn.description}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
content += `</div>`;
|
||||
|
||||
if (data.flows && data.flows.length > 0) {
|
||||
content += `
|
||||
<div class="info-section">
|
||||
<h4>⚡ Поток данных</h4>
|
||||
<div class="flow-diagram">
|
||||
`;
|
||||
data.flows.forEach((flow, index) => {
|
||||
content += `
|
||||
<div class="flow-step">
|
||||
<span>${index + 1}.</span>
|
||||
<span>${flow}</span>
|
||||
${index < data.flows.length - 1 ? '<span class="flow-arrow">↓</span>' : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
content += `</div></div>`;
|
||||
}
|
||||
|
||||
if (data.ports && data.ports.length > 0) {
|
||||
content += `
|
||||
<div class="info-section">
|
||||
<h4>🌐 Порты</h4>
|
||||
<ul>
|
||||
`;
|
||||
data.ports.forEach(port => {
|
||||
content += `<li>${port}</li>`;
|
||||
});
|
||||
content += `</ul></div>`;
|
||||
}
|
||||
|
||||
infoPanelContent.innerHTML = content;
|
||||
infoPanel.classList.add('active');
|
||||
}
|
||||
|
||||
function closeInfoPanel() {
|
||||
infoPanel.classList.remove('active');
|
||||
components.forEach(c => {
|
||||
c.classList.remove('selected');
|
||||
c.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
|
||||
function highlightConnections(componentId, connections) {
|
||||
// Reset all components
|
||||
components.forEach(c => {
|
||||
c.style.opacity = '0.3';
|
||||
});
|
||||
|
||||
// Highlight current component
|
||||
const currentComponent = document.querySelector(`[data-id="${componentId}"]`);
|
||||
if (currentComponent) {
|
||||
currentComponent.style.opacity = '1';
|
||||
}
|
||||
|
||||
// Highlight connected components
|
||||
connections.forEach(conn => {
|
||||
const targetComponent = document.querySelector(`[data-id="${conn.target}"]`);
|
||||
if (targetComponent) {
|
||||
targetComponent.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
components.forEach(component => {
|
||||
const searchData = component.dataset.search.toLowerCase();
|
||||
const title = component.querySelector('h3').textContent.toLowerCase();
|
||||
const description = component.querySelector('.component-description').textContent.toLowerCase();
|
||||
|
||||
if (searchData.includes(searchTerm) ||
|
||||
title.includes(searchTerm) ||
|
||||
description.includes(searchTerm)) {
|
||||
component.classList.remove('hidden');
|
||||
} else {
|
||||
component.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Close info panel on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeInfoPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Close info panel when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.component') && !e.target.closest('.info-panel')) {
|
||||
closeInfoPanel();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,305 @@
|
||||
# Browser Domains Monitoring - Компонентная диаграмма
|
||||
|
||||
## Обзор
|
||||
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Browser Domains Collector"
|
||||
A[Main Loop]
|
||||
B[Window Detection]
|
||||
C[URL Extraction]
|
||||
D[Domain Parsing]
|
||||
E[Web Categorization]
|
||||
F[DLP Rule Check]
|
||||
G[Incident Handler]
|
||||
H[Event Logger]
|
||||
I[Screenshot Capture]
|
||||
end
|
||||
|
||||
subgraph "External Dependencies"
|
||||
J[Windows API]
|
||||
K[Web Category API]
|
||||
L[DLP Policy]
|
||||
M[ActivityWatch API]
|
||||
N[Custom Category Rules]
|
||||
end
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
D --> F
|
||||
E --> F
|
||||
F --> G
|
||||
G --> H
|
||||
G --> I
|
||||
|
||||
B --> J
|
||||
C --> J
|
||||
E --> K
|
||||
F --> L
|
||||
H --> M
|
||||
E --> N
|
||||
F --> N
|
||||
|
||||
style B fill:#ff6b6b
|
||||
style E fill:#4ecdc4
|
||||
style F fill:#4ecdc4
|
||||
style G fill:#ffe66d
|
||||
```
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### Domain Detection Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Browser as Browser Window
|
||||
participant Windows as Windows API
|
||||
participant Collector as Browser Collector
|
||||
participant Parser as Domain Parser
|
||||
participant Category as Category API
|
||||
participant DLP as DLP Engine
|
||||
participant AW as ActivityWatch
|
||||
|
||||
User->>Browser: Navigate to website
|
||||
Browser->>Windows: Window title change
|
||||
Windows->>Collector: Window event
|
||||
Collector->>Collector: Check if browser
|
||||
Collector->>Collector: Extract URL from window
|
||||
Collector->>Parser: Parse domain
|
||||
Parser->>Collector: Return domain
|
||||
Collector->>Category: Get web category
|
||||
Category->>Collector: Return category
|
||||
Collector->>DLP: Check DLP rules
|
||||
alt Violation
|
||||
DLP->>Collector: Trigger incident
|
||||
Collector->>Collector: Capture screenshot
|
||||
Collector->>AW: Send DLP incident
|
||||
else Normal
|
||||
Collector->>AW: Send browsing event
|
||||
end
|
||||
```
|
||||
|
||||
### Domain Categorization Flow
|
||||
```mermaid
|
||||
graph LR
|
||||
A[URL] --> B[Extract Domain]
|
||||
B --> C{Custom Rules?}
|
||||
C -->|Yes| D[Apply Custom Category]
|
||||
C -->|No| E[Call Category API]
|
||||
D --> F[Return Category]
|
||||
E --> F
|
||||
F --> G[Cache Result]
|
||||
G --> H[Use in DLP Check]
|
||||
```
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### Обнаружение и извлечение
|
||||
- `browser_domains_native_collector_get_foregroundwindowcontext()` - контекст активного окна
|
||||
- `browser_domains_native_collector_get_browserurlfromwindow()` - извлечение URL из окна
|
||||
- `browser_domains_native_collector_get_hostfromurl()` - извлечение домена из URL
|
||||
- `browser_domains_native_collector_get_rootdomain()` - получение корневого домена
|
||||
|
||||
### Категоризация
|
||||
- `browser_domains_native_collector_get_webcategory()` - определение категории сайта
|
||||
- `browser_domains_native_collector_load_customcategoryrules()` - загрузка кастомных правил
|
||||
- `browser_domains_native_collector_test_domainlistmatch()` - проверка по спискам доменов
|
||||
|
||||
### DLP проверка
|
||||
- `browser_domains_native_collector_get_dlpdecision()` - решение DLP
|
||||
- `browser_domains_native_collector_test_dlprulematch()` - проверка DLP правила
|
||||
- `browser_domains_native_collector_test_dlpruletimewindow()` - проверка временного окна
|
||||
- `browser_domains_native_collector_should_emitincident()` - решение о создании инцидента
|
||||
|
||||
### Логирование
|
||||
- `browser_domains_native_collector_write_collectorlog()` - лог коллектора
|
||||
- `browser_domains_native_collector_write_dlpincidentlog()` - лог DLP инцидента
|
||||
- `browser_domains_native_collector_send_heartbeat()` - heartbeat
|
||||
- `browser_domains_native_collector_send_categoryheartbeat()` - heartbeat категорий
|
||||
|
||||
### Утилиты
|
||||
- `browser_domains_native_collector_convertto_normalizedurl()` - нормализация URL
|
||||
- `browser_domains_native_collector_test_domainmatch()` - проверка совпадения домена
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Custom Category Rules
|
||||
```json
|
||||
{
|
||||
"custom_categories": {
|
||||
"social_media": [
|
||||
"*.facebook.com",
|
||||
"*.twitter.com",
|
||||
"*.instagram.com"
|
||||
],
|
||||
"news": [
|
||||
"*.news.com",
|
||||
"*.media.com"
|
||||
],
|
||||
"blocked": [
|
||||
"*.malware.com",
|
||||
"*.phishing.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Domain Rules
|
||||
```json
|
||||
{
|
||||
"domain_rules": [
|
||||
{
|
||||
"pattern": "*gambling*",
|
||||
"category": "gambling",
|
||||
"action": "alert",
|
||||
"severity": "medium"
|
||||
},
|
||||
{
|
||||
"pattern": "*adult*",
|
||||
"category": "adult",
|
||||
"action": "block",
|
||||
"severity": "high"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment Config
|
||||
```json
|
||||
{
|
||||
"aw_server_url": "http://aw-server:5600",
|
||||
"bucket_prefix": "aw-watcher-browser-domains",
|
||||
"category_api_enabled": true,
|
||||
"custom_categories_enabled": true,
|
||||
"screenshot_on_incident": true,
|
||||
"heartbeat_interval": 60
|
||||
}
|
||||
```
|
||||
|
||||
## События
|
||||
|
||||
### Browsing Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "browsing",
|
||||
"data": {
|
||||
"url": "https://www.example.com/page",
|
||||
"domain": "example.com",
|
||||
"root_domain": "example.com",
|
||||
"category": "technology",
|
||||
"browser": "chrome.exe",
|
||||
"title": "Example Page Title",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Incident Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "dlp_incident",
|
||||
"source": "browser_domain",
|
||||
"rule_id": "blocked_domains",
|
||||
"severity": "high",
|
||||
"data": {
|
||||
"url": "https://blocked.com",
|
||||
"domain": "blocked.com",
|
||||
"category": "malicious",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01",
|
||||
"screenshot": "path/to/screenshot.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Category Heartbeat
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "category_heartbeat",
|
||||
"data": {
|
||||
"domains_seen": 150,
|
||||
"categories": {
|
||||
"technology": 50,
|
||||
"news": 30,
|
||||
"social_media": 40,
|
||||
"other": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Поддерживаемые браузеры
|
||||
|
||||
- Google Chrome
|
||||
- Mozilla Firefox
|
||||
- Microsoft Edge
|
||||
- Opera
|
||||
- Яндекс.Браузер
|
||||
|
||||
## Зависимости
|
||||
|
||||
### Windows API
|
||||
- Window enumeration
|
||||
- Window title extraction
|
||||
- Process information
|
||||
|
||||
### Внешние сервисы
|
||||
- Web categorization API (опционально)
|
||||
- ActivityWatch HTTP API
|
||||
|
||||
## Алгоритм категоризации
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Domain extracted] --> B{Custom rule match?}
|
||||
B -->|Yes| C[Return custom category]
|
||||
B -->|No| D{Cache hit?}
|
||||
D -->|Yes| E[Return cached category]
|
||||
D -->|No| F[Call category API]
|
||||
F --> G{API success?}
|
||||
G -->|Yes| H[Cache result]
|
||||
G -->|No| I[Return 'uncategorized']
|
||||
H --> J[Return category]
|
||||
I --> J
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Метрики
|
||||
- Количество уникальных доменов
|
||||
- Распределение по категориям
|
||||
- Частота DLP инцидентов
|
||||
- Кэш hit rate для категорий
|
||||
|
||||
### Алерты
|
||||
- Коллектор не активен > 5 минут
|
||||
- Высокий процент blocked категорий
|
||||
- Ошибки category API
|
||||
- Много неизвестных категорий
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- Кэширование категорий доменов
|
||||
- Batch запросы к category API
|
||||
- Debouncing быстрых переходов
|
||||
- Асинхронная отправка событий
|
||||
|
||||
### Настройки производительности
|
||||
```json
|
||||
{
|
||||
"cache_ttl": 86400,
|
||||
"batch_size": 50,
|
||||
"debounce_ms": 1000,
|
||||
"max_events_per_minute": 100
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,375 @@
|
||||
# DLP Events Aggregation - Компонентная диаграмма
|
||||
|
||||
## Обзор
|
||||
Система агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики и отчетности.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Aggregation Pipeline"
|
||||
A[Main Orchestrator]
|
||||
B[Bucket Discovery]
|
||||
C[Event Fetcher]
|
||||
D[Event Parser]
|
||||
E[Normalizer]
|
||||
F[PostgreSQL Writer]
|
||||
G[State Manager]
|
||||
end
|
||||
|
||||
subgraph "Data Sources"
|
||||
H[ActivityWatch API]
|
||||
I[SQLite Buckets]
|
||||
J[PostgreSQL Database]
|
||||
end
|
||||
|
||||
subgraph "Output"
|
||||
K[Aggregated Events]
|
||||
L[Statistics]
|
||||
M[Reports]
|
||||
end
|
||||
|
||||
A --> B
|
||||
A --> C
|
||||
A --> G
|
||||
B --> H
|
||||
C --> H
|
||||
C --> I
|
||||
D --> C
|
||||
E --> D
|
||||
F --> E
|
||||
F --> J
|
||||
G --> J
|
||||
|
||||
F --> K
|
||||
F --> L
|
||||
F --> M
|
||||
|
||||
style A fill:#4ecdc4
|
||||
style F fill:#ff6b6b
|
||||
style G fill:#ffe66d
|
||||
```
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### Aggregation Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Orchestrator as Main Orchestrator
|
||||
participant State as State Manager
|
||||
participant AW as ActivityWatch API
|
||||
participant Fetcher as Event Fetcher
|
||||
participant Parser as Event Parser
|
||||
participant PG as PostgreSQL
|
||||
|
||||
Orchestrator->>State: Load last sync state
|
||||
State->>Orchestrator: Return last timestamp
|
||||
Orchestrator->>AW: List buckets
|
||||
AW->>Orchestrator: Return bucket list
|
||||
loop For each DLP bucket
|
||||
Orchestrator->>AW: Get events since last sync
|
||||
AW->>Fetcher: Return events
|
||||
Fetcher->>Parser: Parse event structure
|
||||
Parser->>Parser: Extract DLP data
|
||||
Parser->>Orchestrator: Return parsed events
|
||||
Orchestrator->>PG: Insert batch of events
|
||||
PG->>Orchestrator: Confirm insert
|
||||
end
|
||||
Orchestrator->>State: Save new sync state
|
||||
State->>PG: Persist state
|
||||
```
|
||||
|
||||
### Event Processing Pipeline
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Raw Event] --> B[Parse JSON]
|
||||
B --> C[Extract DLP Fields]
|
||||
C --> D[Normalize Timestamp]
|
||||
D --> E[Enrich with User/Host]
|
||||
E --> F[Validate Data]
|
||||
F --> G[Batch Prepare]
|
||||
G --> H[PostgreSQL Insert]
|
||||
```
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### Оркестрация
|
||||
- `aggregate_dlp_events_main()` - главная функция агрегации
|
||||
- `aggregate_dlp_events_list_buckets()` - получение списка buckets
|
||||
- `aggregate_dlp_events_get_start_time()` - определение начала периода
|
||||
|
||||
### Получение событий
|
||||
- `aggregate_dlp_events_fetch_bucket_events()` - получение событий из bucket
|
||||
- `aggregate_dlp_events_bucket_stream_type()` - определение типа потока
|
||||
- `aggregate_dlp_events_aw_get_json()` - HTTP запрос к AW API
|
||||
|
||||
### Парсинг
|
||||
- `aggregate_dlp_events_awevent()` - парсинг события AW
|
||||
- `aggregate_dlp_events_parse_timestamp()` - парсинг timestamp
|
||||
- `aggregate_dlp_events_parse_prefixes()` - парсинг префиксов bucket
|
||||
- `aggregate_dlp_events_event_row()` - формирование строки события
|
||||
|
||||
### Нормализация
|
||||
- `aggregate_dlp_events_normalize_base_url()` - нормализация URL AW API
|
||||
- `aggregate_dlp_events_format_aw_timestamp()` - форматирование timestamp
|
||||
- `aggregate_dlp_events_bool_as_int()` - конвертация boolean в int
|
||||
- `aggregate_dlp_events_first_string()` - получение первой строки
|
||||
|
||||
### Батчинг
|
||||
- `aggregate_dlp_events_bucket()` - обработка bucket
|
||||
- `aggregate_dlp_events_build_events_path()` - построение пути к событиям
|
||||
- `aggregate_dlp_events_build_parser()` - создание парсера
|
||||
|
||||
### Соединения
|
||||
- `aggregate_dlp_events_connect_sqlite()` - соединение с SQLite
|
||||
- `aggregate_dlp_events_psycopgconnection()` - соединение с PostgreSQL
|
||||
- `aggregate_dlp_events_psycopgconnection_cursor()` - курсор PostgreSQL
|
||||
- `aggregate_dlp_events_psycopgconnection_commit()` - коммит транзакции
|
||||
|
||||
### Запись
|
||||
- `aggregate_dlp_events_insert_events()` - вставка событий
|
||||
- `aggregate_dlp_events_insert_postgres_events()` - вставка в PostgreSQL
|
||||
- `aggregate_dlp_events_select_buckets()` - выборка buckets
|
||||
- `aggregate_dlp_events_ensure_schema()` - обеспечение схемы БД
|
||||
|
||||
### Управление состоянием
|
||||
- `aggregate_dlp_events_load_state()` - загрузка состояния
|
||||
- `aggregate_dlp_events_save_state()` - сохранение состояния
|
||||
- `aggregate_dlp_events_event_key()` - ключ события
|
||||
|
||||
### Утилиты
|
||||
- `aggregate_dlp_events_utc_now()` - текущее UTC время
|
||||
- `aggregate_dlp_events_ensure_postgres_schema()` - схема PostgreSQL
|
||||
|
||||
## Схема базы данных
|
||||
|
||||
### Таблица dlp_events
|
||||
```sql
|
||||
CREATE TABLE dlp_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
timestamp TIMESTAMP NOT NULL,
|
||||
event_type VARCHAR(50) NOT NULL,
|
||||
source VARCHAR(50) NOT NULL,
|
||||
rule_id VARCHAR(100),
|
||||
severity VARCHAR(20),
|
||||
user_id VARCHAR(100),
|
||||
host_id VARCHAR(100),
|
||||
data JSONB,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
INDEX idx_timestamp (timestamp),
|
||||
INDEX idx_user (user_id),
|
||||
INDEX idx_host (host_id),
|
||||
INDEX idx_severity (severity),
|
||||
INDEX idx_type (event_type)
|
||||
);
|
||||
```
|
||||
|
||||
### Таблица aggregation_state
|
||||
```sql
|
||||
CREATE TABLE aggregation_state (
|
||||
id SERIAL PRIMARY KEY,
|
||||
bucket_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
last_synced_timestamp TIMESTAMP,
|
||||
last_synced_at TIMESTAMP DEFAULT NOW(),
|
||||
events_processed INTEGER DEFAULT 0
|
||||
);
|
||||
```
|
||||
|
||||
### Таблица dlp_statistics
|
||||
```sql
|
||||
CREATE TABLE dlp_statistics (
|
||||
id SERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
user_id VARCHAR(100),
|
||||
host_id VARCHAR(100),
|
||||
event_type VARCHAR(50),
|
||||
severity VARCHAR(20),
|
||||
incident_count INTEGER DEFAULT 0,
|
||||
UNIQUE(date, user_id, host_id, event_type, severity)
|
||||
);
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Config File
|
||||
```json
|
||||
{
|
||||
"aw_base_url": "http://aw-server:5600",
|
||||
"postgres_url": "postgresql://user:pass@localhost:5432/activitywatch",
|
||||
"batch_size": 1000,
|
||||
"sync_interval_minutes": 5,
|
||||
"bucket_prefixes": [
|
||||
"aw-watcher-dlp-endpoint",
|
||||
"aw-watcher-browser-domains",
|
||||
"aw-watcher-email-outbound"
|
||||
],
|
||||
"retention_days": 90
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
AW_BASE_URL=http://aw-server:5600
|
||||
POSTGRES_URL=postgresql://aw:password@localhost:5432/activitywatch
|
||||
BATCH_SIZE=1000
|
||||
SYNC_INTERVAL=300
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
## События
|
||||
|
||||
### Input Event (from ActivityWatch)
|
||||
```json
|
||||
{
|
||||
"id": "event_id",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"duration": 60.0,
|
||||
"data": {
|
||||
"type": "dlp_incident",
|
||||
"source": "clipboard",
|
||||
"rule_id": "credit_card_pattern",
|
||||
"severity": "high",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01",
|
||||
"matched_text": "****-****-****-1234"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Output Event (in PostgreSQL)
|
||||
```sql
|
||||
INSERT INTO dlp_events (
|
||||
timestamp, event_type, source, rule_id,
|
||||
severity, user_id, host_id, data
|
||||
) VALUES (
|
||||
'2024-01-01 12:00:00',
|
||||
'dlp_incident',
|
||||
'clipboard',
|
||||
'credit_card_pattern',
|
||||
'high',
|
||||
'user1',
|
||||
'WORKSTATION01',
|
||||
'{"matched_text": "****-****-****-1234"}'::jsonb
|
||||
);
|
||||
```
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- Batch вставки (по 1000 событий)
|
||||
- Connection pooling к PostgreSQL
|
||||
- Асинхронная обработка
|
||||
- Индексы на частых запросах
|
||||
|
||||
### Метрики производительности
|
||||
```python
|
||||
performance_metrics = {
|
||||
"events_per_second": 100,
|
||||
"batch_insert_time_ms": 50,
|
||||
"api_latency_ms": 20,
|
||||
"postgres_write_latency_ms": 30
|
||||
}
|
||||
```
|
||||
|
||||
### Мониторинг
|
||||
- Время обработки batch
|
||||
- Размер очереди событий
|
||||
- Ошибки соединения с PostgreSQL
|
||||
- Latency API запросов
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
### Retry Strategy
|
||||
```python
|
||||
retry_config = {
|
||||
"max_retries": 3,
|
||||
"backoff_seconds": [1, 5, 15],
|
||||
"retry_on": [
|
||||
"ConnectionError",
|
||||
"TimeoutError",
|
||||
"DatabaseError"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Dead Letter Queue
|
||||
```sql
|
||||
CREATE TABLE dlp_events_failed (
|
||||
id SERIAL PRIMARY KEY,
|
||||
raw_event JSONB,
|
||||
error_message TEXT,
|
||||
failed_at TIMESTAMP DEFAULT NOW(),
|
||||
retry_count INTEGER DEFAULT 0
|
||||
);
|
||||
```
|
||||
|
||||
## Планирование
|
||||
|
||||
### Cron Job
|
||||
```cron
|
||||
*/5 * * * * /usr/bin/python3 /path/to/aggregate_dlp_events.py
|
||||
```
|
||||
|
||||
### Systemd Service
|
||||
```ini
|
||||
[Unit]
|
||||
Description=ActivityWatch DLP Aggregation
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aw-aggregator
|
||||
ExecStart=/usr/bin/python3 /path/to/aggregate_dlp_events.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Отчеты
|
||||
|
||||
### Daily Report
|
||||
```sql
|
||||
SELECT
|
||||
date,
|
||||
event_type,
|
||||
severity,
|
||||
COUNT(*) as incident_count
|
||||
FROM dlp_events
|
||||
WHERE date >= CURRENT_DATE - INTERVAL '7 days'
|
||||
GROUP BY date, event_type, severity
|
||||
ORDER BY date DESC, incident_count DESC;
|
||||
```
|
||||
|
||||
### User Summary
|
||||
```sql
|
||||
SELECT
|
||||
user_id,
|
||||
COUNT(*) as total_incidents,
|
||||
COUNT(CASE WHEN severity = 'high' THEN 1 END) as high_severity,
|
||||
COUNT(CASE WHEN severity = 'medium' THEN 1 END) as medium_severity
|
||||
FROM dlp_events
|
||||
WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
|
||||
GROUP BY user_id
|
||||
ORDER BY total_incidents DESC;
|
||||
```
|
||||
|
||||
## Валидация данных
|
||||
|
||||
### Checks
|
||||
- Timestamp в допустимом диапазоне
|
||||
- Обязательные поля заполнены
|
||||
- Severity в списке допустимых значений
|
||||
- User/Host существуют в справочниках
|
||||
|
||||
### Data Quality
|
||||
```python
|
||||
validation_rules = {
|
||||
"timestamp": "required, past_date",
|
||||
"event_type": "required, in_list",
|
||||
"severity": "required, in_list",
|
||||
"user_id": "required, max_length=100",
|
||||
"host_id": "required, max_length=100"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,264 @@
|
||||
# DLP Endpoint Monitoring - Компонентная диаграмма
|
||||
|
||||
## Обзор
|
||||
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "DLP Endpoint Collector"
|
||||
A[Main Loop]
|
||||
B[Load DLP Policy]
|
||||
C[Deployment Config]
|
||||
D[Clipboard Monitor]
|
||||
E[Print Monitor]
|
||||
F[USB Monitor]
|
||||
G[Rule Engine]
|
||||
H[Enforcement Actions]
|
||||
I[Event Logging]
|
||||
J[Screenshot Capture]
|
||||
end
|
||||
|
||||
subgraph "External Dependencies"
|
||||
K[ActivityWatch API]
|
||||
L[DLP Policy JSON]
|
||||
M[Windows API]
|
||||
N[File System]
|
||||
end
|
||||
|
||||
A --> B
|
||||
A --> C
|
||||
B --> G
|
||||
C --> A
|
||||
|
||||
D --> G
|
||||
E --> G
|
||||
F --> G
|
||||
|
||||
G --> H
|
||||
G --> I
|
||||
G --> J
|
||||
|
||||
H --> M
|
||||
I --> N
|
||||
J --> N
|
||||
|
||||
I --> K
|
||||
A --> K
|
||||
|
||||
B --> L
|
||||
C --> L
|
||||
|
||||
style D fill:#ff6b6b
|
||||
style E fill:#ff6b6b
|
||||
style F fill:#ff6b6b
|
||||
style G fill:#4ecdc4
|
||||
style H fill:#ffe66d
|
||||
```
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### Clipboard Monitoring Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Clipboard as Clipboard API
|
||||
participant Collector as DLP Collector
|
||||
participant RuleEngine as Rule Engine
|
||||
participant AW as ActivityWatch
|
||||
participant FS as File System
|
||||
|
||||
User->>Clipboard: Copy data
|
||||
Clipboard->>Collector: Clipboard change event
|
||||
Collector->>RuleEngine: Evaluate against rules
|
||||
RuleEngine->>RuleEngine: Check patterns
|
||||
alt Pattern Match
|
||||
RuleEngine->>Collector: Trigger incident
|
||||
Collector->>FS: Capture screenshot
|
||||
Collector->>Collector: Apply enforcement
|
||||
Collector->>AW: Send DLP incident
|
||||
else No Match
|
||||
RuleEngine->>Collector: Allow
|
||||
end
|
||||
```
|
||||
|
||||
### Print Monitoring Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant PrintSpooler as Print Spooler
|
||||
participant Collector as DLP Collector
|
||||
participant RuleEngine as Rule Engine
|
||||
participant AW as ActivityWatch
|
||||
|
||||
App->>PrintSpooler: Print job
|
||||
PrintSpooler->>Collector: Print service event
|
||||
Collector->>Collector: Extract document info
|
||||
Collector->>RuleEngine: Evaluate printer rules
|
||||
alt Violation
|
||||
RuleEngine->>Collector: Block print job
|
||||
Collector->>PrintSpooler: Cancel job
|
||||
Collector->>AW: Log incident
|
||||
else Allowed
|
||||
Collector->>AW: Log print activity
|
||||
end
|
||||
```
|
||||
|
||||
### USB Monitoring Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant USB as USB Device
|
||||
participant Windows as Windows API
|
||||
participant Collector as DLP Collector
|
||||
participant RuleEngine as Rule Engine
|
||||
participant AW as ActivityWatch
|
||||
|
||||
User->>USB: Insert USB drive
|
||||
USB->>Windows: Device connect
|
||||
Windows->>Collector: USB device event
|
||||
Collector->>RuleEngine: Check USB rules
|
||||
alt Write Blocked
|
||||
RuleEngine->>Collector: Block write
|
||||
Collector->>Windows: Prevent write
|
||||
Collector->>AW: Log blocked attempt
|
||||
else Write Allowed
|
||||
Collector->>AW: Log file copy
|
||||
end
|
||||
```
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### Основные функции
|
||||
- `dlp_endpoint_signals_collector_main()` - главный цикл коллектора
|
||||
- `dlp_endpoint_signals_collector_load_dlppolicy()` - загрузка DLP правил
|
||||
- `dlp_endpoint_signals_collector_get_deploymentconfig()` - чтение конфигурации
|
||||
|
||||
### Мониторинг
|
||||
- `dlp_endpoint_signals_collector_evaluate_clipboardrules()` - проверка clipboard
|
||||
- `dlp_endpoint_signals_collector_evaluate_printrules()` - проверка печати
|
||||
- `dlp_endpoint_signals_collector_evaluate_usbrules()` - проверка USB
|
||||
|
||||
### Принудительные действия
|
||||
- `dlp_endpoint_signals_collector_invoke_clipboardenforcement()` - блокировка clipboard
|
||||
- `dlp_endpoint_signals_collector_invoke_printjobenforcement()` - блокировка печати
|
||||
- `dlp_endpoint_signals_collector_invoke_usbwriteblockenforcement()` - блокировка USB
|
||||
|
||||
### Логирование
|
||||
- `dlp_endpoint_signals_collector_write_endpointlog()` - запись логов коллектора
|
||||
- `dlp_endpoint_signals_collector_send_dlpincidentheartbeat()` - heartbeat инцидентов
|
||||
- `dlp_endpoint_signals_collector_capture_incidentscreenshot()` - захват скриншота
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### DLP Policy Structure
|
||||
```json
|
||||
{
|
||||
"clipboard_rules": [
|
||||
{
|
||||
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
|
||||
"description": "Credit card numbers",
|
||||
"severity": "high",
|
||||
"action": "block"
|
||||
}
|
||||
],
|
||||
"print_rules": [
|
||||
{
|
||||
"printer_match": "*",
|
||||
"document_keywords": ["confidential", "secret"],
|
||||
"action": "block"
|
||||
}
|
||||
],
|
||||
"usb_rules": [
|
||||
{
|
||||
"device_id": "*",
|
||||
"action": "block_write"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment Config
|
||||
```json
|
||||
{
|
||||
"aw_server_url": "http://aw-server:5600",
|
||||
"bucket_prefix": "aw-watcher-dlp-endpoint",
|
||||
"heartbeat_interval": 60,
|
||||
"screenshot_on_incident": true,
|
||||
"enforcement_enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
## События
|
||||
|
||||
### DLP Incident Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "dlp_incident",
|
||||
"source": "clipboard",
|
||||
"rule_id": "credit_card_pattern",
|
||||
"severity": "high",
|
||||
"data": {
|
||||
"matched_text": "****-****-****-1234",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01",
|
||||
"application": "chrome.exe",
|
||||
"screenshot": "path/to/screenshot.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Heartbeat Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "heartbeat",
|
||||
"status": "running",
|
||||
"incidents_count": 5,
|
||||
"last_incident": "2024-01-01T11:55:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Зависимости
|
||||
|
||||
### Windows API
|
||||
- Clipboard API
|
||||
- Print Spooler API
|
||||
- USB Device Notification API
|
||||
- Process API
|
||||
|
||||
### External Services
|
||||
- ActivityWatch HTTP API
|
||||
- File system (для скриншотов и логов)
|
||||
|
||||
## Развертывание
|
||||
|
||||
### Требования
|
||||
- Windows 10/11
|
||||
- PowerShell 5.1+
|
||||
- ActivityWatch installed
|
||||
- Административские права (для enforcement)
|
||||
|
||||
### Установка
|
||||
```powershell
|
||||
# Копирование коллектора
|
||||
Copy-Item dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
|
||||
|
||||
# Настройка scheduled task
|
||||
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Метрики
|
||||
- Количество инцидентов по типам (clipboard/print/USB)
|
||||
- Частота срабатываний правил
|
||||
- Успешность enforcement действий
|
||||
- Heartbeat статус
|
||||
|
||||
### Алерты
|
||||
- Коллектор не отправляет heartbeat > 5 минут
|
||||
- Высокая частота DLP инцидентов
|
||||
- Ошибки enforcement действий
|
||||
@@ -0,0 +1,338 @@
|
||||
# ActivityWatch-Russian - Обзор компонентов и связей
|
||||
|
||||
## Полная архитектура системы
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Windows Clients"
|
||||
subgraph "Workstation 1"
|
||||
W1A[DLP Endpoint Collector]
|
||||
W1B[Browser Domains Collector]
|
||||
W1C[Email Outbound Collector]
|
||||
W1D[Worktime Session Collector]
|
||||
end
|
||||
|
||||
subgraph "Workstation 2"
|
||||
W2A[DLP Endpoint Collector]
|
||||
W2B[Browser Domains Collector]
|
||||
W2C[Email Outbound Collector]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Linux Server"
|
||||
S1[ActivityWatch Server]
|
||||
S2[PostgreSQL Database]
|
||||
S3[WebUI with RU Patches]
|
||||
end
|
||||
|
||||
subgraph "Integration Layer"
|
||||
I1[pfSense Poller]
|
||||
I2[DLP Aggregation Scripts]
|
||||
I3[Prometheus Exporter]
|
||||
end
|
||||
|
||||
subgraph "Monitoring Stack"
|
||||
M1[Prometheus]
|
||||
M2[Grafana]
|
||||
M3[SQL Exporter]
|
||||
end
|
||||
|
||||
subgraph "External Systems"
|
||||
E1[pfSense Firewall]
|
||||
E2[Domain Controller]
|
||||
end
|
||||
|
||||
W1A -->|Events| S1
|
||||
W1B -->|Events| S1
|
||||
W1C -->|Events| S1
|
||||
W1D -->|Events| S1
|
||||
W2A -->|Events| S1
|
||||
W2B -->|Events| S1
|
||||
W2C -->|Events| S1
|
||||
|
||||
S1 -->|Store| S2
|
||||
S1 -->|Serve| S3
|
||||
|
||||
I1 -->|Logs| E1
|
||||
I1 -->|Events| S1
|
||||
|
||||
I2 -->|Read/Write| S2
|
||||
I2 -->|Read| S1
|
||||
|
||||
I3 -->|Metrics| S1
|
||||
I3 -->|Scrape| M1
|
||||
|
||||
M1 -->|Query| I3
|
||||
M2 -->|Query| M1
|
||||
M2 -->|Query| S2
|
||||
M3 -->|Query| S2
|
||||
|
||||
E2 -->|Deploy| W1A
|
||||
E2 -->|Deploy| W1B
|
||||
E2 -->|Deploy| W1C
|
||||
E2 -->|Deploy| W2A
|
||||
E2 -->|Deploy| W2B
|
||||
E2 -->|Deploy| W2C
|
||||
|
||||
style S1 fill:#4ecdc4
|
||||
style S2 fill:#ff6b6b
|
||||
style M1 fill:#ffe66d
|
||||
style M2 fill:#95e1d3
|
||||
```
|
||||
|
||||
## Матрица связей компонентов
|
||||
|
||||
| Компонент | Тип | Подключается к | Протокол | Назначение |
|
||||
|-----------|-----|----------------|-----------|------------|
|
||||
| DLP Endpoint Collector | Windows Client | AW Server | HTTP API | Отправка DLP событий |
|
||||
| Browser Domains Collector | Windows Client | AW Server | HTTP API | Отправка браузерных событий |
|
||||
| Email Outbound Collector | Windows Client | AW Server | HTTP API | Отправка email событий |
|
||||
| Worktime Session Collector | Windows Client | AW Server | HTTP API | Отправка сессий |
|
||||
| ActivityWatch Server | Server | PostgreSQL | TCP | Хранение данных |
|
||||
| ActivityWatch Server | Server | WebUI | HTTP | Отдача интерфейса |
|
||||
| pfSense Poller | Integration | pfSense | HTTP API | Сбор логов firewall |
|
||||
| pfSense Poller | Integration | AW Server | HTTP API | Отправка событий |
|
||||
| DLP Aggregation | Integration | PostgreSQL | TCP | Обработка событий |
|
||||
| DLP Aggregation | Integration | AW Server | HTTP API | Чтение событий |
|
||||
| Prometheus Exporter | Integration | AW Server | HTTP API | Сбор метрик |
|
||||
| Prometheus Exporter | Integration | Prometheus | HTTP | Отдача метрик |
|
||||
| Prometheus | Monitoring | Exporter | HTTP | Scraping метрик |
|
||||
| Grafana | Monitoring | Prometheus | HTTP | Запросы метрик |
|
||||
| Grafana | Monitoring | PostgreSQL | TCP | Прямые запросы |
|
||||
| SQL Exporter | Monitoring | PostgreSQL | TCP | SQL запросы |
|
||||
|
||||
## Потоки данных по уровням
|
||||
|
||||
### Уровень 1: Сбор данных (Windows)
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Windows Clients │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Workstation 1 Workstation 2 Workstation N │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
|
||||
│ │ DLP Collector│ │ DLP Collector│ │ DLP Coll.│ │
|
||||
│ │ Browser Coll.│ │ Browser Coll.│ │ Browser │ │
|
||||
│ │ Email Coll. │ │ Email Coll. │ │ Email │ │
|
||||
│ │ Worktime │ │ │ │ │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────────┼───────────────────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────────┼────────────────────────────┘
|
||||
│ HTTP API
|
||||
▼
|
||||
```
|
||||
|
||||
### Уровень 2: Хранение и обработка (Linux)
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Linux Server Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ AW Server │◄────── Events │
|
||||
│ │ (Rust) │ │
|
||||
│ └───────┬───────┘ │
|
||||
│ │ Store │
|
||||
│ ▼ │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ PostgreSQL │ │
|
||||
│ └───────┬───────┘ │
|
||||
│ │ │
|
||||
└────────────────────────────┼───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
```
|
||||
|
||||
### Уровень 3: Интеграции
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Integration Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ pfSense │ │ DLP Aggreg. │ │ Prometheus │ │
|
||||
│ │ Poller │ │ Scripts │ │ Exporter │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ pfSense FW │ │ PostgreSQL │ │ AW Server │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Уровень 4: Визуализация
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Monitoring & Visualization │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Prometheus │◄───┤ Exporter │ │ Grafana │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ │ Query │ Query │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Grafana │ │ PostgreSQL │ │
|
||||
│ │ Dashboards │ │ │ │
|
||||
│ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Сценарии использования
|
||||
|
||||
### Сценарий 1: DLP инцидент
|
||||
```
|
||||
User copies sensitive data
|
||||
↓
|
||||
DLP Endpoint Collector detects
|
||||
↓
|
||||
Evaluates against rules
|
||||
↓
|
||||
Creates incident event
|
||||
↓
|
||||
Sends to AW Server
|
||||
↓
|
||||
Stored in PostgreSQL
|
||||
↓
|
||||
Aggregated by scripts
|
||||
↓
|
||||
Visible in Grafana DLP Dashboard
|
||||
```
|
||||
|
||||
### Сценарий 2: Мониторинг браузера
|
||||
```
|
||||
User visits website
|
||||
↓
|
||||
Browser Domains Collector detects
|
||||
↓
|
||||
Extracts domain
|
||||
↓
|
||||
Categorizes website
|
||||
↓
|
||||
Checks DLP rules
|
||||
↓
|
||||
Sends event to AW Server
|
||||
↓
|
||||
Visible in WebUI Dashboard
|
||||
```
|
||||
|
||||
### Сценарий 3: Метрики
|
||||
```
|
||||
Prometheus scrapes Exporter
|
||||
↓
|
||||
Exporter queries AW API
|
||||
↓
|
||||
Collects metrics
|
||||
↓
|
||||
Returns in Prometheus format
|
||||
↓
|
||||
Prometheus stores metrics
|
||||
↓
|
||||
Grafana visualizes in dashboards
|
||||
```
|
||||
|
||||
## Зависимости развертывания
|
||||
|
||||
### Минимальная конфигурация
|
||||
```
|
||||
1 Linux Server:
|
||||
- ActivityWatch Server
|
||||
- PostgreSQL
|
||||
- WebUI with patches
|
||||
|
||||
1+ Windows Workstations:
|
||||
- DLP Endpoint Collector
|
||||
- Browser Domains Collector
|
||||
- Email Outbound Collector
|
||||
```
|
||||
|
||||
### Полная конфигурация
|
||||
```
|
||||
1 Linux Server:
|
||||
- ActivityWatch Server
|
||||
- PostgreSQL
|
||||
- WebUI with patches
|
||||
- DLP Aggregation Scripts
|
||||
- Prometheus Exporter
|
||||
|
||||
1+ Windows Workstations:
|
||||
- DLP Endpoint Collector
|
||||
- Browser Domains Collector
|
||||
- Email Outbound Collector
|
||||
- Worktime Session Collector
|
||||
|
||||
1 pfSense Firewall:
|
||||
- pfSense Poller
|
||||
|
||||
1 Monitoring Server:
|
||||
- Prometheus
|
||||
- Grafana
|
||||
- SQL Exporter
|
||||
```
|
||||
|
||||
## Порты и протоколы
|
||||
|
||||
| Компонент | Порт | Протокол | Направление |
|
||||
|-----------|------|----------|-------------|
|
||||
| ActivityWatch API | 5600 | HTTP | Inbound |
|
||||
| ActivityWatch WebSocket | 5666 | WebSocket | Inbound |
|
||||
| PostgreSQL | 5432 | TCP | Inbound |
|
||||
| Prometheus | 9090 | HTTP | Inbound |
|
||||
| Grafana | 3000 | HTTP | Inbound |
|
||||
| Prometheus Exporter | 9398 | HTTP | Inbound |
|
||||
| pfSense API | 443 | HTTPS | Outbound |
|
||||
|
||||
## Резервное копирование
|
||||
|
||||
### PostgreSQL Backup
|
||||
```bash
|
||||
# Daily backup
|
||||
pg_dump activitywatch > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Restore
|
||||
psql activitywatch < backup_20240101.sql
|
||||
```
|
||||
|
||||
### AW Server Backup
|
||||
```bash
|
||||
# Backup SQLite databases (if used)
|
||||
cp /var/lib/activitywatch/*.db /backup/
|
||||
|
||||
# Backup configuration
|
||||
cp /etc/activitywatch/config.toml /backup/
|
||||
```
|
||||
|
||||
## Масштабирование
|
||||
|
||||
### Горизонтальное масштабирование
|
||||
- Добавление Windows workstation не требует изменений сервера
|
||||
- Каждый workstation автономно отправляет события
|
||||
- Server обрабатывает события от множества клиентов
|
||||
|
||||
### Вертикальное масштабирование
|
||||
- Увеличение ресурсов PostgreSQL для больших объемов данных
|
||||
- Разделение AW Server и PostgreSQL на разные машины
|
||||
- Добавление реплик PostgreSQL для высокой доступности
|
||||
|
||||
## Мониторинг системы
|
||||
|
||||
### Ключевые метрики
|
||||
- Количество активных хостов
|
||||
- Скорость поступления событий
|
||||
- Размер базы данных
|
||||
- Latency обработки событий
|
||||
- Статус коллекторов
|
||||
|
||||
### Алерты
|
||||
- Коллектор неактивен > 5 минут
|
||||
- Высокий процент DLP инцидентов
|
||||
- PostgreSQL connection pool exhausted
|
||||
- Диск > 80% заполнен
|
||||
- AW Server недоступен
|
||||
@@ -0,0 +1,373 @@
|
||||
# Prometheus Metrics Exporter - Компонентная диаграмма
|
||||
|
||||
## Обзор
|
||||
Экспорт метрик ActivityWatch в формате Prometheus для мониторинга и визуализации в Grafana.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Prometheus Exporter"
|
||||
A[HTTP Server]
|
||||
B[Metrics Collector]
|
||||
C[Bucket Fetcher]
|
||||
D[Event Counter]
|
||||
E[Host Tracker]
|
||||
F[Metrics Formatter]
|
||||
G[Registry]
|
||||
end
|
||||
|
||||
subgraph "Data Sources"
|
||||
H[ActivityWatch API]
|
||||
I[Buckets Data]
|
||||
J[Events Data]
|
||||
end
|
||||
|
||||
subgraph "Consumers"
|
||||
K[Prometheus]
|
||||
L[Grafana]
|
||||
end
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
B --> D
|
||||
B --> E
|
||||
C --> H
|
||||
C --> I
|
||||
D --> H
|
||||
D --> J
|
||||
E --> H
|
||||
F --> B
|
||||
F --> G
|
||||
G --> A
|
||||
|
||||
K --> A
|
||||
L --> K
|
||||
|
||||
style A fill:#ff6b6b
|
||||
style B fill:#4ecdc4
|
||||
style G fill:#ffe66d
|
||||
```
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### Metrics Collection Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Prometheus as Prometheus
|
||||
participant Server as HTTP Server
|
||||
participant Collector as Metrics Collector
|
||||
participant AW as ActivityWatch API
|
||||
participant Registry as Metrics Registry
|
||||
|
||||
Prometheus->>Server: GET /metrics
|
||||
Server->>Collector: Trigger collection
|
||||
Collector->>AW: Get buckets list
|
||||
AW->>Collector: Return buckets
|
||||
loop For each bucket
|
||||
Collector->>AW: Get bucket info
|
||||
AW->>Collector: Return bucket data
|
||||
Collector->>Collector: Calculate metrics
|
||||
Collector->>Registry: Update metrics
|
||||
end
|
||||
Collector->>Server: Collection complete
|
||||
Server->>Registry: Format as Prometheus
|
||||
Registry->>Server: Return formatted metrics
|
||||
Server->>Prometheus: Return metrics text
|
||||
```
|
||||
|
||||
### Metrics Update Flow
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Scrape Request] --> B[Fetch Buckets]
|
||||
B --> C[Get Bucket Info]
|
||||
C --> D[Count Events]
|
||||
D --> E[Track Active Hosts]
|
||||
E --> F[Calculate Rates]
|
||||
F --> G[Update Registry]
|
||||
G --> H[Format Output]
|
||||
H --> I[Return to Prometheus]
|
||||
```
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### Инициализация
|
||||
- `aw_activitywatch_activitywatchexporter_init()` - инициализация exporter
|
||||
- `aw_activitywatch_activitywatchexporter()` - главный класс exporter
|
||||
- `aw_activitywatch_main()` - точка входа
|
||||
|
||||
### Сбор данных
|
||||
- `aw_activitywatch_activitywatchexporter_get_buckets()` - получение списка buckets
|
||||
- `aw_activitywatch_activitywatchexporter_get_bucket_info()` - информация о bucket
|
||||
- `aw_activitywatch_activitywatchexporter_get_bucket_events()` - события bucket
|
||||
- `aw_activitywatch_activitywatchexporter_collect_metrics()` - сбор всех метрик
|
||||
|
||||
### HTTP Server
|
||||
- Запуск HTTP сервера на порту 9398
|
||||
- Endpoint `/metrics` для Prometheus
|
||||
- Endpoint `/health` для health checks
|
||||
|
||||
### Метрики
|
||||
```python
|
||||
# Bucket metrics
|
||||
aw_bucket_events_total{bucket_id, host} - общее количество событий
|
||||
aw_bucket_events_last_timestamp{bucket_id} - timestamp последнего события
|
||||
|
||||
# Host metrics
|
||||
aw_host_active{host} - хост активен (1/0)
|
||||
aw_host_last_seen{host} - время последней активности
|
||||
|
||||
# Collector metrics
|
||||
aw_collector_heartbeat{host, collector_type} - heartbeat коллектора
|
||||
aw_collector_status{host, collector_type, status} - статус коллектора
|
||||
|
||||
# Aggregation metrics
|
||||
aw_aggregation_events_processed_total - обработано событий
|
||||
aw_aggregation_last_success_timestamp - последний успешный запуск
|
||||
|
||||
# Exporter metrics
|
||||
aw_exporter_scrape_duration_seconds - время обработки scrape
|
||||
aw_exporter_up - exporter доступен (1/0)
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Config
|
||||
```json
|
||||
{
|
||||
"aw_base_url": "http://aw-server:5600",
|
||||
"listen_address": "0.0.0.0",
|
||||
"listen_port": 9398,
|
||||
"scrape_interval_seconds": 60,
|
||||
"bucket_prefixes": [
|
||||
"aw-watcher-dlp-endpoint",
|
||||
"aw-watcher-browser-domains",
|
||||
"aw-watcher-email-outbound",
|
||||
"aw-watcher-window"
|
||||
],
|
||||
"cache_ttl_seconds": 30
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
AW_BASE_URL=http://aw-server:5600
|
||||
EXPORTER_PORT=9398
|
||||
SCRAPE_INTERVAL=60
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
## Пример метрик
|
||||
|
||||
### Prometheus Format
|
||||
```
|
||||
# HELP aw_bucket_events_total Total number of events in bucket
|
||||
# TYPE aw_bucket_events_total gauge
|
||||
aw_bucket_events_total{bucket_id="aw-watcher-dlp-endpoint-WORKSTATION01",host="WORKSTATION01"} 1523.0
|
||||
aw_bucket_events_total{bucket_id="aw-watcher-browser-domains-WORKSTATION01",host="WORKSTATION01"} 3421.0
|
||||
|
||||
# HELP aw_host_active Host is active (1 or 0)
|
||||
# TYPE aw_host_active gauge
|
||||
aw_host_active{host="WORKSTATION01"} 1.0
|
||||
aw_host_active{host="WORKSTATION02"} 0.0
|
||||
|
||||
# HELP aw_host_last_seen Unix timestamp of last activity
|
||||
# TYPE aw_host_last_seen gauge
|
||||
aw_host_last_seen{host="WORKSTATION01"} 1704110400.0
|
||||
aw_host_last_seen{host="WORKSTATION02"} 1704100000.0
|
||||
|
||||
# HELP aw_exporter_scrape_duration_seconds Duration of scrape
|
||||
# TYPE aw_exporter_scrape_duration_seconds gauge
|
||||
aw_exporter_scrape_duration_seconds 0.523
|
||||
|
||||
# HELP aw_exporter_up Exporter is up (1 or 0)
|
||||
# TYPE aw_exporter_up gauge
|
||||
aw_exporter_up 1.0
|
||||
```
|
||||
|
||||
## Prometheus Configuration
|
||||
|
||||
### scrape_config
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'activitywatch'
|
||||
static_configs:
|
||||
- targets: ['localhost:9398']
|
||||
scrape_interval: 60s
|
||||
scrape_timeout: 30s
|
||||
metrics_path: /metrics
|
||||
```
|
||||
|
||||
### Alerting Rules
|
||||
```yaml
|
||||
groups:
|
||||
- name: activitywatch_alerts
|
||||
rules:
|
||||
- alert: AWCollectorDown
|
||||
expr: aw_host_active == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "ActivityWatch collector down on {{ $labels.host }}"
|
||||
|
||||
- alert: AWExporterDown
|
||||
expr: aw_exporter_up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "ActivityWatch exporter is down"
|
||||
|
||||
- alert: AWHighEventRate
|
||||
expr: rate(aw_bucket_events_total[5m]) > 100
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High event rate on {{ $labels.host }}"
|
||||
```
|
||||
|
||||
## Grafana Dashboard Queries
|
||||
|
||||
### Active Hosts
|
||||
```promql
|
||||
sum(aw_host_active) by (host)
|
||||
```
|
||||
|
||||
### Events per Bucket
|
||||
```promql
|
||||
aw_bucket_events_total
|
||||
```
|
||||
|
||||
### Event Rate
|
||||
```promql
|
||||
rate(aw_bucket_events_total[5m])
|
||||
```
|
||||
|
||||
### Host Last Seen
|
||||
```promql
|
||||
aw_host_last_seen
|
||||
```
|
||||
|
||||
### Collector Status
|
||||
```promql
|
||||
aw_collector_status{status="running"}
|
||||
```
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- Кэширование ответов AW API
|
||||
- Batch запросы к buckets
|
||||
- Connection pooling
|
||||
- Асинхронная обработка
|
||||
|
||||
### Метрики производительности
|
||||
```python
|
||||
performance_metrics = {
|
||||
"scrape_duration_seconds": 0.5,
|
||||
"aw_api_latency_seconds": 0.2,
|
||||
"metrics_count": 50,
|
||||
"cache_hit_rate": 0.95
|
||||
}
|
||||
```
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
### Dockerfile
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY collectors/aw_activitywatch.py .
|
||||
|
||||
EXPOSE 9398
|
||||
|
||||
CMD ["python", "aw_activitywatch.py"]
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
```yaml
|
||||
services:
|
||||
aw-exporter:
|
||||
build: ./sql-exporter
|
||||
ports:
|
||||
- "9398:9398"
|
||||
environment:
|
||||
- AW_BASE_URL=http://aw-server:5600
|
||||
- EXPORTER_PORT=9398
|
||||
depends_on:
|
||||
- aw-server
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
### Endpoint: /health
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"aw_server_reachable": true,
|
||||
"last_scrape_duration_seconds": 0.523,
|
||||
"metrics_count": 50
|
||||
}
|
||||
```
|
||||
|
||||
### Health Check Script
|
||||
```bash
|
||||
#!/bin/bash
|
||||
response=$(curl -s http://localhost:9398/health)
|
||||
status=$(echo $response | jq -r '.status')
|
||||
|
||||
if [ "$status" == "healthy" ]; then
|
||||
echo "Exporter is healthy"
|
||||
exit 0
|
||||
else
|
||||
echo "Exporter is unhealthy"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Логирование
|
||||
|
||||
### Log Format
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"level": "INFO",
|
||||
"message": "Metrics collection completed",
|
||||
"duration_seconds": 0.523,
|
||||
"buckets_processed": 10,
|
||||
"metrics_generated": 50
|
||||
}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
- DEBUG - детальная информация о сборе
|
||||
- INFO - нормальная работа
|
||||
- WARNING - не критичные проблемы
|
||||
- ERROR - ошибки при сборе метрик
|
||||
- CRITICAL - exporter недоступен
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Рекомендации
|
||||
- Запускать за reverse proxy (nginx)
|
||||
- Ограничить доступ по IP
|
||||
- Использовать HTTPS в production
|
||||
- Не экспортировать чувствительные данные
|
||||
|
||||
### Nginx Config
|
||||
```nginx
|
||||
location /metrics {
|
||||
allow 10.0.0.0/8;
|
||||
deny all;
|
||||
proxy_pass http://localhost:9398/metrics;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,337 @@
|
||||
# WebUI Russian Localization Patches - Компонентная диаграмма
|
||||
|
||||
## Обзор
|
||||
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Patch System"
|
||||
A[Patch Loader]
|
||||
B[DOM Injection]
|
||||
C[Style Injection]
|
||||
D[Navigation Patches]
|
||||
E[DLP Center Injection]
|
||||
F[Host Groups Center]
|
||||
G[Category Builder Patch]
|
||||
H[Text Translation]
|
||||
end
|
||||
|
||||
subgraph "ActivityWatch WebUI"
|
||||
I[Original WebUI]
|
||||
J[DOM Structure]
|
||||
K[Vue Components]
|
||||
L[Router]
|
||||
M[Axios API]
|
||||
end
|
||||
|
||||
subgraph "Patch Targets"
|
||||
N[Navigation Menu]
|
||||
O[Activity View]
|
||||
P[Category Builder]
|
||||
Q[Buckets View]
|
||||
R[Settings]
|
||||
end
|
||||
|
||||
A --> B
|
||||
A --> C
|
||||
A --> D
|
||||
A --> E
|
||||
A --> F
|
||||
A --> G
|
||||
A --> H
|
||||
|
||||
B --> J
|
||||
C --> J
|
||||
D --> L
|
||||
E --> K
|
||||
F --> K
|
||||
G --> K
|
||||
H --> K
|
||||
|
||||
D --> N
|
||||
E --> K
|
||||
F --> K
|
||||
G --> P
|
||||
H --> O
|
||||
|
||||
I --> J
|
||||
I --> K
|
||||
I --> L
|
||||
|
||||
style B fill:#ff6b6b
|
||||
style C fill:#ff6b6b
|
||||
style E fill:#4ecdc4
|
||||
style F fill:#4ecdc4
|
||||
style H fill:#ffe66d
|
||||
```
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### Patch Application Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as Browser
|
||||
participant WebUI as AW WebUI
|
||||
participant Loader as Patch Loader
|
||||
participant DOM as DOM Manipulator
|
||||
participant API as AW API
|
||||
|
||||
User->>WebUI: Load page
|
||||
WebUI->>Loader: Trigger patch application
|
||||
Loader->>Loader: Check route
|
||||
Loader->>DOM: Inject DLP center
|
||||
DOM->>DOM: Modify navigation
|
||||
DOM->>DOM: Add Russian styles
|
||||
Loader->>DOM: Translate text elements
|
||||
DOM->>DOM: Replace text with Russian
|
||||
Loader->>API: Fetch Russian data
|
||||
API->>Loader: Return translated data
|
||||
Loader->>DOM: Update UI with Russian
|
||||
DOM->>User: Render patched UI
|
||||
```
|
||||
|
||||
### DLP Center Injection Flow
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Route Check] --> B{Is DLP route?}
|
||||
B -->|Yes| C[Load DLP Center Component]
|
||||
B -->|No| D[Skip DLP injection]
|
||||
C --> E[Inject into DOM]
|
||||
E --> F[Bind Events]
|
||||
F --> G[Load DLP Data]
|
||||
G --> H[Render DLP UI]
|
||||
```
|
||||
|
||||
## Ключевые функции
|
||||
|
||||
### Загрузка патчей
|
||||
- `aw_ru_patch_applypatch()` - применение патча
|
||||
- `aw_ru_patch_scheduleapplypatch()` - отложенное применение
|
||||
- `aw_ru_patch_ensurehostgroupsdata()` - обеспечение данных групп хостов
|
||||
|
||||
### Инъекция компонентов
|
||||
- `aw_ru_patch_injectdlpnavigation()` - инъекция DLP навигации
|
||||
- `aw_ru_patch_injectdlpreviewcenter()` - инъекция центра просмотра DLP
|
||||
- `aw_ru_patch_injectdlpalertscenter()` - инъекция центра алертов
|
||||
- `aw_ru_patch_injecthostgroupscenter()` - инъекция центра групп хостов
|
||||
- `aw_ru_patch_injectpveauditcenter()` - инъекция центра аудита PVE
|
||||
|
||||
### Патчи навигации
|
||||
- `aw_ru_patch_findprimarynavlist()` - поиск главного меню
|
||||
- `aw_ru_patch_hidenoisenavigation()` - скрытие лишних пунктов
|
||||
- `aw_ru_patch_removebaddlplinks()` - удаление битых DLP ссылок
|
||||
- `aw_ru_patch_updatedlplinks()` - обновление DLP ссылок
|
||||
|
||||
### Патчи представлений
|
||||
- `aw_ru_patch_enforcesafeactivityviewforpvehost()` - безопасный вид для PVE
|
||||
- `aw_ru_patch_collapsereviewevents()` - сворачивание событий ревью
|
||||
- `aw_ru_patch_collapseruleevents()` - сворачивание событий правил
|
||||
- `aw_ru_patch_loadbucketevents()` - загрузка событий bucket
|
||||
|
||||
### Группы хостов
|
||||
- `aw_ru_patch_getdefaulthostgroupsconfig()` - конфигурация групп по умолчанию
|
||||
- `aw_ru_patch_matchhostgroup()` - сопоставление группы хоста
|
||||
- `aw_ru_patch_renderhostgroupcards()` - рендер карточек групп
|
||||
- `aw_ru_patch_hosthasbucketprefix()` - проверка префикса bucket
|
||||
|
||||
### Перевод
|
||||
- `aw_ru_patch_translateattributes()` - перевод атрибутов
|
||||
- `aw_ru_patch_normalizetext()` - нормализация текста
|
||||
- `aw_ru_patch_escapehtml()` - экранирование HTML
|
||||
|
||||
### Утилиты
|
||||
- `aw_ru_patch_replacetext()` - замена текста
|
||||
- `aw_ru_patch_walk()` - обход DOM
|
||||
- `aw_ru_patch_ishomeroute()` - проверка домашнего маршрута
|
||||
- `aw_ru_patch_ispvelikehost()` - проверка PVE хоста
|
||||
|
||||
## Структура патчей
|
||||
|
||||
### Патчи по маршрутам
|
||||
```javascript
|
||||
const routePatches = {
|
||||
'/': 'home-patch',
|
||||
'/activity': 'activity-patch',
|
||||
'/buckets': 'buckets-patch',
|
||||
'/category-builder': 'category-builder-patch',
|
||||
'/dlp/review': 'dlp-review-patch',
|
||||
'/dlp/rules': 'dlp-rules-patch',
|
||||
'/dlp/alerts': 'dlp-alerts-patch'
|
||||
};
|
||||
```
|
||||
|
||||
### Инъекции DLP компонентов
|
||||
```javascript
|
||||
const dlpInjections = {
|
||||
navigation: 'inject-dlp-nav',
|
||||
reviewCenter: 'inject-dlp-review',
|
||||
rulesManager: 'inject-dlp-rules',
|
||||
alertsCenter: 'inject-dlp-alerts'
|
||||
};
|
||||
```
|
||||
|
||||
### Русификация
|
||||
```javascript
|
||||
const translations = {
|
||||
'Activity': 'Активность',
|
||||
'Buckets': 'Бакеты',
|
||||
'Duration': 'Длительность',
|
||||
'Events': 'События',
|
||||
'Settings': 'Настройки'
|
||||
};
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Host Groups Config
|
||||
```json
|
||||
{
|
||||
"host_groups": [
|
||||
{
|
||||
"id": "workstations",
|
||||
"name": "Рабочие станции",
|
||||
"hosts": ["WORKSTATION01", "WORKSTATION02"],
|
||||
"color": "#4ecdc4"
|
||||
},
|
||||
{
|
||||
"id": "servers",
|
||||
"name": "Серверы",
|
||||
"hosts": ["SERVER01", "SERVER02"],
|
||||
"color": "#ff6b6b"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Settings
|
||||
```json
|
||||
{
|
||||
"dlp_enabled": true,
|
||||
"review_center_enabled": true,
|
||||
"alerts_center_enabled": true,
|
||||
"auto_refresh_interval": 30,
|
||||
"default_severity_filter": "all"
|
||||
}
|
||||
```
|
||||
|
||||
## Компоненты UI
|
||||
|
||||
### DLP Review Center
|
||||
- Список инцидентов
|
||||
- Фильтрация по типам
|
||||
- Детальный просмотр
|
||||
- Архивирование
|
||||
- Экспорт
|
||||
|
||||
### DLP Rules Manager
|
||||
- Список правил
|
||||
- Создание/редактирование
|
||||
- Включение/выключение
|
||||
- Тестирование правил
|
||||
- Импорт/экспорт
|
||||
|
||||
### DLP Alerts Center
|
||||
- Алерты в реальном времени
|
||||
- Группировка по серьезности
|
||||
- История алертов
|
||||
- Подписки на алерты
|
||||
|
||||
### Host Groups Center
|
||||
- Управление группами хостов
|
||||
- Визуализация по группам
|
||||
- Агрегированная статистика
|
||||
- Сравнение групп
|
||||
|
||||
## Интеграция с ActivityWatch
|
||||
|
||||
### API Endpoints
|
||||
```javascript
|
||||
// Получение DLP инцидентов
|
||||
GET /api/0/buckets/{bucket_id}/events?filters=dlp_incident
|
||||
|
||||
// Получение правил DLP
|
||||
GET /api/0/dlp/rules
|
||||
|
||||
// Сохранение инцидента
|
||||
POST /api/0/dlp/incident
|
||||
|
||||
// Получение групп хостов
|
||||
GET /api/0/host_groups
|
||||
```
|
||||
|
||||
### Bucket Prefixes
|
||||
```
|
||||
aw-watcher-dlp-endpoint-*
|
||||
aw-watcher-browser-domains-*
|
||||
aw-watcher-email-outbound-*
|
||||
```
|
||||
|
||||
## Стили
|
||||
|
||||
### Russian UI Styles
|
||||
```css
|
||||
.ru-font {
|
||||
font-family: 'Segoe UI', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.ru-nav-item {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ru-dlp-card {
|
||||
border-left: 4px solid #ff6b6b;
|
||||
background: #fff5f5;
|
||||
}
|
||||
```
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- Lazy loading патчей
|
||||
- Кэширование переводов
|
||||
- Debouncing обновлений DOM
|
||||
- Virtual scrolling для больших списков
|
||||
|
||||
### Метрики
|
||||
- Время применения патча
|
||||
- Количество модификаций DOM
|
||||
- Частота перерисовки
|
||||
- Размер загружаемых переводов
|
||||
|
||||
## Отладка
|
||||
|
||||
### Логирование патчей
|
||||
```javascript
|
||||
console.log('[AW-RU-Patch] Applying patch:', patchName);
|
||||
console.log('[AW-RU-Patch] Route:', currentRoute);
|
||||
console.log('[AW-RU-Patch] Elements modified:', count);
|
||||
```
|
||||
|
||||
### Режим разработки
|
||||
```javascript
|
||||
const DEBUG_MODE = true;
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
window.AW_RU_PATCHES = {
|
||||
applied: [],
|
||||
skipped: [],
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Совместимость
|
||||
|
||||
### Поддерживаемые версии ActivityWatch
|
||||
- v0.12.x
|
||||
- v0.13.x
|
||||
- v0.14.x
|
||||
|
||||
### Поддерживаемые браузеры
|
||||
- Chrome 90+
|
||||
- Firefox 88+
|
||||
- Edge 90+
|
||||
- Safari 14+
|
||||
@@ -0,0 +1,126 @@
|
||||
# DLP Enforcement (action: "block")
|
||||
|
||||
## Обзор
|
||||
|
||||
Phase 2.5 расширяет DLP endpoint collector функциями **активного предотвращения** (enforcement).
|
||||
При `action: "block"` в правиле DLP-политики коллектор не только регистрирует инцидент, но и выполняет блокирующее действие:
|
||||
|
||||
| Канал | Действие при `block` |
|
||||
|-----------|-----------------------------------------------------------|
|
||||
| clipboard | Очистка буфера обмена (`Set-Clipboard -Value $null`) |
|
||||
| usb | Перевод USB-диска в read-only (`Set-Disk -IsReadOnly`) |
|
||||
| print | Отмена задания печати (`Remove-CimInstance Win32_PrintJob`)|
|
||||
|
||||
Во всех случаях пользователь получает Windows-уведомление (balloon notification) с описанием причины блокировки.
|
||||
|
||||
## Конфигурация политики
|
||||
|
||||
Формат `dlp-policy.json` не изменился — поле `action` в правиле теперь поддерживает значение `"block"` наряду с `"alert"` (по умолчанию).
|
||||
|
||||
### Пример: блокировка USB записи
|
||||
|
||||
```json
|
||||
{
|
||||
"defaults": {
|
||||
"enabled": true,
|
||||
"action": "alert",
|
||||
"severity": "medium",
|
||||
"cooldownSeconds": 300
|
||||
},
|
||||
"endpoint": {
|
||||
"usb": [
|
||||
{
|
||||
"id": "block-all-usb-write",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"message": "Запись на USB-носитель заблокирована политикой DLP"
|
||||
}
|
||||
],
|
||||
"clipboard": [
|
||||
{
|
||||
"id": "block-pdn-clipboard",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"regexPatterns": [
|
||||
"\\b\\d{3}-\\d{3}-\\d{3}\\s?\\d{2}\\b",
|
||||
"\\b\\d{4}\\s?\\d{6}\\b"
|
||||
],
|
||||
"minLength": 8,
|
||||
"message": "Буфер обмена очищен: обнаружены персональные данные (СНИЛС/паспорт)"
|
||||
}
|
||||
],
|
||||
"print": [
|
||||
{
|
||||
"id": "block-confidential-print",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"documentRegex": "(?i)(конфиденциально|секретно|confidential|restricted)",
|
||||
"message": "Печать заблокирована: документ содержит метку конфиденциальности"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пример: только мониторинг (без блокировки)
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoint": {
|
||||
"usb": [
|
||||
{
|
||||
"id": "monitor-usb",
|
||||
"action": "alert",
|
||||
"severity": "medium",
|
||||
"message": "Обнаружено подключение USB-носителя"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Телеметрия
|
||||
|
||||
Каждый инцидент с enforcement записывается в bucket `aw-dlp-incidents_<host>` с дополнительным полем:
|
||||
|
||||
```json
|
||||
{
|
||||
"ruleId": "block-all-usb-write",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"signalType": "usb_insert",
|
||||
"enforced": true,
|
||||
"driveLetter": "E:",
|
||||
"volumeName": "FLASH_DRIVE"
|
||||
}
|
||||
```
|
||||
|
||||
- `enforced: true` — блокировка выполнена успешно
|
||||
- `enforced: false` — блокировка не удалась (недостаточно прав, устройство недоступно и т.д.)
|
||||
|
||||
## Требования
|
||||
|
||||
- **Clipboard block**: Не требует повышенных прав.
|
||||
- **USB write-block**: Требует запуск от имени администратора (для `Set-Disk -IsReadOnly`). При запуске без прав блокировка не сработает, но инцидент будет зарегистрирован с `enforced: false`.
|
||||
- **Print block**: Требует права на отмену заданий печати (обычно — SYSTEM или администратор принт-сервера).
|
||||
|
||||
## Уведомления
|
||||
|
||||
При каждой блокировке пользователю показывается Windows balloon notification:
|
||||
|
||||
| Канал | Заголовок |
|
||||
|-----------|--------------------------------------|
|
||||
| clipboard | `DLP: буфер обмена очищен` |
|
||||
| usb | `DLP: USB заблокирован для записи` |
|
||||
| print | `DLP: печать заблокирована` |
|
||||
|
||||
Текст уведомления берётся из поля `message` правила политики.
|
||||
|
||||
## Rollback
|
||||
|
||||
Для отключения enforcement без изменения кода — смените `action` с `"block"` на `"alert"` в `dlp-policy.json`. Все правила продолжат мониторинг без блокировки.
|
||||
|
||||
Для USB, переведённого в read-only, восстановление:
|
||||
```powershell
|
||||
Get-Disk | Where-Object { $_.BusType -eq 'USB' -and $_.IsReadOnly } | Set-Disk -IsReadOnly $false
|
||||
```
|
||||
@@ -32,6 +32,42 @@
|
||||
- File-operation telemetry (create/delete/rename/archive hints) — прототип внедрён (`windows/file-operations-collector.ps1`).
|
||||
- Central incident aggregation/export — прототип внедрён (`scripts/aggregate_dlp_events.py`, `docs/dlp-aggregator.md`).
|
||||
|
||||
### Phase 2.5 — Enforcement (внедрено)
|
||||
|
||||
- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено.
|
||||
- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено.
|
||||
- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено.
|
||||
- Windows balloon notification пользователю при блокировке — внедрено.
|
||||
- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено.
|
||||
- Документация: `docs/dlp-enforcement.md`.
|
||||
|
||||
### Phase 2.5 — Email Outbound Collector (внедрено)
|
||||
|
||||
- Мониторинг исходящей почты через Outlook COM (Sent Items polling) — внедрено.
|
||||
- SMTP network connection detection (порты 25/587/465/2525) — внедрено.
|
||||
- DLP-правила `endpoint.email[]` (regex по теме, получателям, вложениям, externalOnly) — внедрено.
|
||||
- Enforcement: перемещение в Drafts при `action: "block"` (Outlook mode) — внедрено.
|
||||
- Приватность: тема/получатели как SHA256, тело не читается — внедрено.
|
||||
- Документация: `docs/email-outbound-collector.md`.
|
||||
|
||||
### Phase 2.5 — Enforcement (внедрено)
|
||||
|
||||
- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено.
|
||||
- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено.
|
||||
- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено.
|
||||
- Windows balloon notification пользователю при блокировке — внедрено.
|
||||
- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено.
|
||||
- Документация: `docs/dlp-enforcement.md`.
|
||||
|
||||
### Phase 2.5 — Email Outbound Collector (внедрено)
|
||||
|
||||
- Мониторинг исходящей почты через Outlook COM (Sent Items polling) — внедрено.
|
||||
- SMTP network connection detection (порты 25/587/465/2525) — внедрено.
|
||||
- DLP-правила `endpoint.email[]` (regex по теме, получателям, вложениям, externalOnly) — внедрено.
|
||||
- Enforcement: перемещение в Drafts при `action: "block"` (Outlook mode) — внедрено.
|
||||
- Приватность: тема/получатели как SHA256, тело не читается — внедрено.
|
||||
- Документация: `docs/email-outbound-collector.md`.
|
||||
|
||||
### Phase 3
|
||||
|
||||
- Policy engine service (server-side), versioned policies, approval workflow.
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# DLP Reliability Roadmap
|
||||
|
||||
## Scope
|
||||
Roadmap for improving runtime reliability of:
|
||||
- `windows/dlp-endpoint-signals-collector.ps1`
|
||||
- `windows/file-operations-collector.ps1`
|
||||
|
||||
Date: 2026-05-04
|
||||
|
||||
---
|
||||
|
||||
## Stage 1 (1-2 days): Quick wins
|
||||
|
||||
### 1) Disk queue + sender loop + retry/backoff/jitter
|
||||
|
||||
**Goal:** no data loss on temporary network/server outages.
|
||||
|
||||
**Tasks**
|
||||
- Add local append-only queue file per collector (`*.jsonl`) under ProgramData logs/artifacts root.
|
||||
- Write events to queue first, then send asynchronously.
|
||||
- Implement sender loop:
|
||||
- reads oldest unsent records,
|
||||
- sends in small batches,
|
||||
- marks sent records,
|
||||
- compacts queue periodically.
|
||||
- Implement retry policy with exponential backoff + jitter.
|
||||
|
||||
**Acceptance criteria**
|
||||
- When API is unavailable, queue grows and collector keeps running.
|
||||
- When API recovers, queued events are flushed automatically.
|
||||
- No collector crash during repeated network failures.
|
||||
|
||||
### 2) `eventId` + dedupe contract
|
||||
|
||||
**Goal:** at-least-once delivery without logical duplicates.
|
||||
|
||||
**Tasks**
|
||||
- Add `eventId` (UUID), `eventCreatedAt`, `collectorType`, `hostname` to every payload.
|
||||
- Define server dedupe contract:
|
||||
- dedupe key = `eventId`,
|
||||
- TTL for dedupe cache,
|
||||
- idempotent processing semantics.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Retried sends do not create duplicate incidents/events in downstream storage.
|
||||
- Payload schema documentation updated.
|
||||
|
||||
### 3) Basic metrics/logging
|
||||
|
||||
**Goal:** visibility into health and data delivery.
|
||||
|
||||
**Tasks**
|
||||
- Emit counters/gauges to log and heartbeat:
|
||||
- `queueDepth`,
|
||||
- `oldestUnsentAgeSec`,
|
||||
- `eventsEnqueued`,
|
||||
- `eventsSent`,
|
||||
- `sendFailures`,
|
||||
- `lastSendStatus`.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Operators can identify stuck queue and send failures from logs only.
|
||||
|
||||
---
|
||||
|
||||
## Stage 2: Hardening
|
||||
|
||||
### 1) Circuit breaker + health probes
|
||||
|
||||
**Tasks**
|
||||
- Add transport circuit breaker (Closed/Open/HalfOpen).
|
||||
- Open breaker after N consecutive failures.
|
||||
- In Open state perform probe every M seconds.
|
||||
- Close breaker on successful probe.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Reduced request storm during outage.
|
||||
- Deterministic recovery behavior after outage.
|
||||
|
||||
### 2) Watcher auto-recreate
|
||||
|
||||
**Tasks**
|
||||
- Handle `FileSystemWatcher` error/overflow events.
|
||||
- Recreate watcher and subscriptions automatically.
|
||||
- Keep watchdog timer to ensure watcher health.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Watcher resumes after overflow without manual restart.
|
||||
|
||||
### 3) Last-known-good policy
|
||||
|
||||
**Tasks**
|
||||
- Validate new policy before apply.
|
||||
- Cache last valid policy with checksum/version.
|
||||
- Rollback to cached policy on parse/validation errors.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Broken policy cannot stop detection loop.
|
||||
|
||||
---
|
||||
|
||||
## Stage 3: Reliability operations
|
||||
|
||||
### 1) Chaos tests
|
||||
|
||||
Scenarios:
|
||||
- network disconnect,
|
||||
- API 5xx bursts,
|
||||
- slow disk / queue write delay,
|
||||
- headless UI context,
|
||||
- forced collector restart.
|
||||
|
||||
**Acceptance criteria**
|
||||
- For each scenario, documented expected behavior and observed result.
|
||||
- No silent data loss in tested outage windows.
|
||||
|
||||
### 2) SLO + error budget process
|
||||
|
||||
**Initial SLO proposals**
|
||||
- Event delivery latency P95 < 120s under normal conditions.
|
||||
- Data loss = 0 for outages shorter than 30 minutes (with available disk).
|
||||
- Collector liveness heartbeat every `pollSeconds * 3` max.
|
||||
|
||||
**Process**
|
||||
- Define SLI dashboards.
|
||||
- Define release gates tied to error budget burn.
|
||||
- Freeze risky changes when budget exhausted.
|
||||
|
||||
---
|
||||
|
||||
## Suggested implementation order inside repository
|
||||
|
||||
1. `file-operations-collector.ps1`: queue + sender + metrics (simpler flow).
|
||||
2. `dlp-endpoint-signals-collector.ps1`: queue + sender + metrics.
|
||||
3. Shared helper module extraction (`windows/lib/aw-transport.psm1`) for queue, retry, breaker.
|
||||
4. Policy cache and validation.
|
||||
5. Chaos test scripts and runbook.
|
||||
|
||||
---
|
||||
|
||||
## Deliverables checklist
|
||||
|
||||
- [ ] Transport queue implementation in both collectors.
|
||||
- [ ] Payload schema update with `eventId`.
|
||||
- [ ] Dedupe contract documented for server side.
|
||||
- [ ] Metrics fields added to heartbeat/logs.
|
||||
- [ ] Circuit breaker implemented.
|
||||
- [ ] Watcher auto-recreate implemented.
|
||||
- [ ] Last-known-good policy implemented.
|
||||
- [ ] Chaos test runbook and results.
|
||||
- [ ] SLO/error budget document adopted.
|
||||
@@ -0,0 +1,164 @@
|
||||
# Email Outbound Collector
|
||||
|
||||
## Обзор
|
||||
|
||||
Мониторинг исходящей почты на Windows-эндпоинтах. Два режима работы:
|
||||
|
||||
| Режим | Источник | Данные |
|
||||
|----------|--------------------------------|-------------------------------------------------------|
|
||||
| outlook | Outlook COM (Sent Items) | Subject, From, To/CC, вложения, размер тела |
|
||||
| smtp | `Get-NetTCPConnection` | SMTP-соединения (порты 25/587/465/2525), процесс |
|
||||
|
||||
По умолчанию `Mode = 'both'` — оба режима активны одновременно.
|
||||
|
||||
## Запуск
|
||||
|
||||
```powershell
|
||||
# С deployment-config.json (штатный вариант)
|
||||
.\email-outbound-collector.ps1
|
||||
|
||||
# С явными параметрами
|
||||
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -ServerPort 5600 -Mode outlook
|
||||
|
||||
# Только SMTP мониторинг (без Outlook)
|
||||
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -Mode smtp
|
||||
```
|
||||
|
||||
### Параметры
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------------|-----------------------------------------|---------------------------------|
|
||||
| `-ConfigPath` | `C:\ProgramData\AWatch-rus\deployment-config.json` | Путь к конфигу |
|
||||
| `-ServerHost` | из конфига | Адрес AW-сервера |
|
||||
| `-ServerPort` | из конфига / 5600 | Порт AW-сервера |
|
||||
| `-PolicyPath` | из конфига / `dlp-policy.json` | Путь к DLP-политике |
|
||||
| `-Mode` | `both` | `outlook`, `smtp`, или `both` |
|
||||
| `-PollSeconds` | из конфига / 10 | Интервал опроса |
|
||||
|
||||
## AW Buckets
|
||||
|
||||
- `aw-email-monitor_<host>` — все email-события (signal heartbeats)
|
||||
- `aw-dlp-incidents_<host>` — инциденты при срабатывании DLP-правил
|
||||
|
||||
## DLP-политика: секция `endpoint.email`
|
||||
|
||||
Добавляется в существующий `dlp-policy.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoint": {
|
||||
"email": [
|
||||
{
|
||||
"id": "block-external-attachments",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"minAttachments": 1,
|
||||
"externalOnly": true,
|
||||
"internalDomain": "@company.ru",
|
||||
"message": "Запрещена отправка вложений на внешние адреса"
|
||||
},
|
||||
{
|
||||
"id": "alert-confidential-subject",
|
||||
"action": "alert",
|
||||
"severity": "medium",
|
||||
"subjectRegex": "(?i)(конфиденциально|секретно|для служебного пользования)",
|
||||
"message": "Обнаружена отправка письма с пометкой конфиденциальности"
|
||||
},
|
||||
{
|
||||
"id": "alert-personal-data",
|
||||
"action": "alert",
|
||||
"severity": "high",
|
||||
"recipientRegex": "(?i)(gmail\\.com|mail\\.ru|yandex\\.ru|yahoo\\.com)",
|
||||
"minAttachments": 1,
|
||||
"message": "Отправка вложений на личную почту"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Параметры правил
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|-------------------|--------|-----------------------------------------------------------|
|
||||
| `id` | string | Уникальный ID правила (обязательно) |
|
||||
| `action` | string | `alert` (по умолчанию) или `block` |
|
||||
| `severity` | string | `low`, `medium`, `high`, `critical` |
|
||||
| `subjectRegex` | string | Regex по теме письма |
|
||||
| `recipientRegex` | string | Regex по списку получателей |
|
||||
| `senderRegex` | string | Regex по адресу отправителя |
|
||||
| `attachmentRegex` | string | Regex по именам вложений |
|
||||
| `minAttachments` | int | Минимальное количество вложений для срабатывания |
|
||||
| `minBodyLength` | int | Минимальная длина тела письма |
|
||||
| `externalOnly` | bool | Срабатывать только на внешних получателей |
|
||||
| `internalDomain` | string | Домен организации (используется с `externalOnly`) |
|
||||
| `cooldownSeconds` | int | Cooldown между повторными инцидентами |
|
||||
| `message` | string | Текст уведомления пользователю и в инцидент |
|
||||
|
||||
## Enforcement (action: "block")
|
||||
|
||||
**Outlook mode**: письмо перемещается из Sent Items в Drafts. Пользователь получает balloon notification.
|
||||
|
||||
**SMTP mode**: только уведомление (перехват SMTP-соединения на сетевом уровне не реализуем из PowerShell). Инцидент записывается с `enforced: false`.
|
||||
|
||||
## Телеметрия
|
||||
|
||||
### Heartbeat `email_sent` (Outlook mode)
|
||||
```json
|
||||
{
|
||||
"signalType": "email_sent",
|
||||
"subject": "<sha256 hash>",
|
||||
"sender": "user@company.ru",
|
||||
"recipientCount": 3,
|
||||
"recipients": "<sha256 hash>",
|
||||
"attachmentCount": 2,
|
||||
"attachmentNames": "report.xlsx; data.csv",
|
||||
"bodyLength": 1520,
|
||||
"collectionMode": "outlook"
|
||||
}
|
||||
```
|
||||
|
||||
### Heartbeat `smtp_connection` (SMTP mode)
|
||||
```json
|
||||
{
|
||||
"signalType": "smtp_connection",
|
||||
"remoteAddress": "74.125.205.108",
|
||||
"remotePort": 587,
|
||||
"processId": 12340,
|
||||
"processName": "OUTLOOK",
|
||||
"collectionMode": "smtp"
|
||||
}
|
||||
```
|
||||
|
||||
### Incident
|
||||
```json
|
||||
{
|
||||
"ruleId": "block-external-attachments",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"signalType": "email_outbound",
|
||||
"subject": "<sha256>",
|
||||
"attachmentCount": 2,
|
||||
"enforced": true
|
||||
}
|
||||
```
|
||||
|
||||
## Приватность
|
||||
|
||||
- Тема и получатели записываются как SHA256-хеш (не открытый текст).
|
||||
- Тело письма не читается и не хранится — записывается только длина.
|
||||
- Имена вложений записываются открытым текстом (для DLP-анализа).
|
||||
|
||||
## Интеграция в ensemble
|
||||
|
||||
Добавьте в `launch-watchers.ps1` или Task Scheduler:
|
||||
|
||||
```powershell
|
||||
Start-Process powershell.exe -ArgumentList '-ExecutionPolicy Bypass -File "C:\ProgramData\AWatch-rus\email-outbound-collector.ps1"' -WindowStyle Hidden
|
||||
```
|
||||
|
||||
## Требования
|
||||
|
||||
- **Outlook mode**: Microsoft Outlook установлен и настроен для текущего пользователя.
|
||||
- **SMTP mode**: Не требует дополнительного ПО. Работает на уровне TCP-соединений.
|
||||
- **Enforcement (block)**: Outlook mode — требует доступ к COM объекту Outlook.
|
||||
@@ -0,0 +1,256 @@
|
||||
# ActivityWatch-Russian: Knowledge Graph Documentation
|
||||
|
||||
## Что такое Knowledge Graph?
|
||||
|
||||
**Knowledge Graph** (граф знаний) - это визуальное представление связей между компонентами программного проекта. Для ActivityWatch-Russian граф показывает:
|
||||
|
||||
- **Функции и модули** как узлы (nodes)
|
||||
- **Вызовы и зависимости** как связи (edges)
|
||||
- **Кластеры** как сообщества связанных функций (communities)
|
||||
|
||||
## Зачем это нужно?
|
||||
|
||||
1. **Понимание архитектуры** - увидеть структуру проекта без чтения кода
|
||||
2. **Поиск зависимостей** - понять, какие компоненты влияют друг на друга
|
||||
3. **Выявление изоляции** - найти функции, которые не связаны с остальным кодом
|
||||
4. **Документация** - автоматическая генерация обзора системы
|
||||
|
||||
## Как был построен граф?
|
||||
|
||||
Граф построен с помощью инструмента **graphify** методом **AST extraction**:
|
||||
|
||||
1. **Сканирование файлов** - найдено 29 кодовых файлов (PowerShell, Python, JavaScript)
|
||||
2. **Анализ синтаксиса** - извлечены функции, классы, вызовы
|
||||
3. **Построение графа** - 404 узла (функции), 933 связи (вызовы)
|
||||
4. **Кластеризация** - 27 сообществ по схожести связей
|
||||
|
||||
## Структура проекта по сообществам
|
||||
|
||||
### 1. DLP Endpoint Monitoring (62 nodes)
|
||||
**Мониторинг конечных точек DLP**
|
||||
|
||||
Функции для отслеживания:
|
||||
- **Clipboard** - буфер обмена
|
||||
- **Print** - задания на печать
|
||||
- **USB** - запись на USB накопители
|
||||
|
||||
Ключевые файлы:
|
||||
- `windows/dlp-endpoint-signals-collector.ps1`
|
||||
- `windows/dlp-policy-test.json`
|
||||
|
||||
### 2. WebUI Russian Localization & Patches (56 nodes)
|
||||
**Русификация веб-интерфейса**
|
||||
|
||||
Патчи для ActivityWatch WebUI:
|
||||
- Перевод интерфейса на русский язык
|
||||
- Скрытие лишних элементов навигации
|
||||
- Инъекция стилей для RU локали
|
||||
- Поддержка кириллицы
|
||||
|
||||
Ключевые файлы:
|
||||
- `aw-server/aw-ru-patch.js`
|
||||
- `aw-server/aw-sw-cleanup.js`
|
||||
|
||||
### 3. Browser Domains Monitoring (54 nodes)
|
||||
**Мониторинг доменов браузеров**
|
||||
|
||||
Отслеживание посещаемых сайтов:
|
||||
- Определение домена из URL
|
||||
- Категоризация веб-ресурсов
|
||||
- Проверка DLP правил для доменов
|
||||
- Скриншоты при нарушениях
|
||||
|
||||
Ключевые файлы:
|
||||
- `windows/browser-domains-native-collector.ps1`
|
||||
|
||||
### 4. DLP Events Aggregation (38 nodes)
|
||||
**Агрегация событий DLP**
|
||||
|
||||
Сбор и обработка инцидентов:
|
||||
- Чтение событий из ActivityWatch buckets
|
||||
- Нормализация данных
|
||||
- Запись в PostgreSQL
|
||||
- Генерация отчетов
|
||||
|
||||
Ключевые файлы:
|
||||
- `scripts/aggregate_dlp_events.py`
|
||||
|
||||
### 5. DLP Review Center (34 nodes)
|
||||
**Центр просмотра инцидентов DLP**
|
||||
|
||||
WebUI компоненты для:
|
||||
- Просмотра списка инцидентов
|
||||
- Управления правилами DLP
|
||||
- Архивирования событий
|
||||
- Фильтрации по хостам
|
||||
|
||||
### 6. Email Outbound Monitoring (34 nodes)
|
||||
**Мониторинг исходящей почты**
|
||||
|
||||
Отслеживание email:
|
||||
- Outlook Sent Items
|
||||
- SMTP соединения
|
||||
- Проверка DLP правил для email
|
||||
- Блокировка нарушений
|
||||
|
||||
Ключевые файлы:
|
||||
- `windows/email-outbound-collector.ps1`
|
||||
|
||||
### 7. Prometheus Metrics Exporter (12 nodes)
|
||||
**Экспорт метрик для Prometheus**
|
||||
|
||||
Сбор метрик ActivityWatch:
|
||||
- Количество событий по buckets
|
||||
- Активность хостов
|
||||
- Статистика collectors
|
||||
- HTTP endpoint для Prometheus
|
||||
|
||||
Ключевые файлы:
|
||||
- `grafana-1c/sql-exporter/collectors/aw_activitywatch.py`
|
||||
|
||||
### 8. pfSense Firewall Integration (12 nodes)
|
||||
**Интеграция с pfSense**
|
||||
|
||||
Сбор данных с firewall:
|
||||
- HTTP API опрос
|
||||
- Парсинг логов pfSense
|
||||
- Нормализация данных
|
||||
- Отправка в ActivityWatch
|
||||
|
||||
Ключевые файлы:
|
||||
- `pfsense/pfsense-aw-poller.py`
|
||||
|
||||
### 9. Migration Scripts (14 nodes)
|
||||
**Скрипты миграции**
|
||||
|
||||
Обновление путей и конфигураций:
|
||||
- Перенос данных между версиями
|
||||
- Обновление конфигурационных файлов
|
||||
- Конвертация путей
|
||||
|
||||
Ключевые файлы:
|
||||
- `windows/migrate-awatch-rus-paths.ps1`
|
||||
|
||||
### 10. Worktime Session Tracking (10 nodes)
|
||||
**Отслеживание рабочих сессий**
|
||||
|
||||
Учет рабочего времени:
|
||||
- Определение начала/конца сессии
|
||||
- Учет перерывов
|
||||
- Агрегация по дням
|
||||
|
||||
Ключевые файлы:
|
||||
- `windows/worktime-session-collector.ps1`
|
||||
|
||||
### 11. Deployment Automation (6 nodes)
|
||||
**Автоматизация развертывания**
|
||||
|
||||
Скрипты деплоя:
|
||||
- `windows/deploy-domain-users.ps1` - доменная развертка
|
||||
- `windows/deploy-single-user.ps1` - одиночный пользователь
|
||||
- `windows/deploy-ensemble.ps1` - групповое развертывание
|
||||
|
||||
## Как пользоваться графом?
|
||||
|
||||
### Интерактивная визуализация
|
||||
Откройте файл `graphify-out/index.html` в браузере:
|
||||
|
||||
- **Zoom** - колесо мыши
|
||||
- **Pan** - перетаскивание
|
||||
- **Click node** - детали узла
|
||||
- **Search** - поиск по названию функции
|
||||
|
||||
### Фильтрация по сообществам
|
||||
Каждое сообщество имеет свой цвет:
|
||||
- Синий - DLP мониторинг
|
||||
- Зеленый - WebUI патчи
|
||||
- Красный - Collectors
|
||||
- Желтый - Утилиты
|
||||
|
||||
### Поиск зависимостей
|
||||
1. Найдите функцию в графе
|
||||
2. Посмотрите на исходящие связи (что вызывает)
|
||||
3. Посмотрите на входящие связи (кто вызывает)
|
||||
|
||||
## Интерпретация связей
|
||||
|
||||
### Высокая связность (hub nodes)
|
||||
Функции с большим количеством связей:
|
||||
- `*_get_deploymentconfig` - чтение конфигурации
|
||||
- `*_invoke_awjsonpost` - отправка данных в ActivityWatch
|
||||
- `*_ensure_bucket` - создание bucket
|
||||
|
||||
### Изолированные компоненты
|
||||
Маленькие сообщества (1-3 nodes) могут быть:
|
||||
- Утилитными функциями
|
||||
- Зависимостями от внешних библиотек
|
||||
- Устаревшим кодом
|
||||
|
||||
## Статистика проекта
|
||||
|
||||
| Метрика | Значение |
|
||||
|---------|----------|
|
||||
| Всего файлов | 29 кодовых файлов |
|
||||
| Всего функций | 404 |
|
||||
| Всего связей | 933 |
|
||||
| Сообществ | 27 |
|
||||
| Средний размер сообщества | 15 nodes |
|
||||
| Самое большое сообщество | 62 nodes (DLP Endpoint) |
|
||||
|
||||
## Технологии по типам файлов
|
||||
|
||||
- **PowerShell (.ps1)** - Windows collectors, deployment
|
||||
- **Python (.py)** - aggregation, exporters, pfSense integration
|
||||
- **JavaScript (.js)** - WebUI patches
|
||||
- **JSON** - конфигурации, policies
|
||||
|
||||
## Рекомендации по архитектуре
|
||||
|
||||
### Сильные стороны
|
||||
1. **Четкая модульность** - каждый collector в своем сообществе
|
||||
2. **Изоляция DLP** - отдельные компоненты для разных типов мониторинга
|
||||
3. **Унификация** - общие паттерны в collector'ах
|
||||
|
||||
### Возможные улучшения
|
||||
1. **Дублирование** - несколько сообществ с похожими функциями (WebUI patches)
|
||||
2. **Интеграция** - слабые связи между некоторыми компонентами
|
||||
3. **Документация** - не все функции имеют явные назначения
|
||||
|
||||
## Обновление графа
|
||||
|
||||
Для пересборки графа после изменений кода:
|
||||
|
||||
```bash
|
||||
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian
|
||||
graphify .
|
||||
```
|
||||
|
||||
Для инкрементального обновления (только измененные файлы):
|
||||
|
||||
```bash
|
||||
graphify . --update
|
||||
```
|
||||
|
||||
## Полезные запросы
|
||||
|
||||
### Найти путь между функциями
|
||||
```bash
|
||||
graphify path "dlp_endpoint_signals_collector_get_deploymentconfig" "aggregate_dlp_events_main"
|
||||
```
|
||||
|
||||
### Объяснить функцию
|
||||
```bash
|
||||
graphify explain "browser_domains_native_collector_get_hostfromurl"
|
||||
```
|
||||
|
||||
### Поиск по вопросу
|
||||
```bash
|
||||
graphify query "Как работает мониторинг clipboard?"
|
||||
```
|
||||
|
||||
## Ссылки
|
||||
|
||||
- **Интерактивный граф**: `graphify-out/index.html`
|
||||
- **Отчет**: `graphify-out/GRAPH_REPORT.md`
|
||||
- **JSON граф**: `graphify-out/graph.json`
|
||||
- **Graphify документация**: https://github.com/brevity-x/graphify
|
||||
@@ -0,0 +1,75 @@
|
||||
# Анализ DLP-скриптов PowerShell (работоспособность)
|
||||
|
||||
Дата анализа: **2026-05-04 (UTC)**
|
||||
|
||||
## Проверенный scope
|
||||
|
||||
- `windows/dlp-endpoint-signals-collector.ps1`
|
||||
- `windows/file-operations-collector.ps1`
|
||||
- `windows/dlp-policy.example.json`
|
||||
- `windows/web-category-rules.example.json`
|
||||
|
||||
## Ключевой итог
|
||||
|
||||
DLP-скрипты в целом рабочие по архитектуре (heartbeat в ActivityWatch, policy-driven правила, cooldown, enforcement), но есть **критичный риск misconfiguration** и несколько эксплуатационных рисков.
|
||||
|
||||
---
|
||||
|
||||
## Что точно хорошо
|
||||
|
||||
1. В обоих коллекторах включены `Set-StrictMode -Version Latest` и `$ErrorActionPreference = 'Stop'`.
|
||||
2. Есть отправка событий в отдельные bucket’ы (`aw-dlp-endpoint-signals_*`, `aw-dlp-incidents_*`, `aw-file-operations_*`).
|
||||
3. В endpoint-коллекторе реализованы:
|
||||
- правила по буферу обмена / USB / печати,
|
||||
- suppression через cooldown (`Should-EmitByCooldown`),
|
||||
- опциональный screenshot capture при инциденте.
|
||||
4. В file collector есть наблюдение за `Desktop/Documents/Downloads` через `FileSystemWatcher`.
|
||||
|
||||
---
|
||||
|
||||
## Найденные проблемы и риски
|
||||
|
||||
### 1) Критично: дефолтный путь конфига в endpoint-скрипте не совпадает с проектом
|
||||
|
||||
- `dlp-endpoint-signals-collector.ps1` использует по умолчанию:
|
||||
- `C:\ProgramData\AWatch-rus\deployment-config.json`
|
||||
- Остальной проект использует namespace `AWatch-rus` (`C:\ProgramData\AWatch-rus\...`).
|
||||
|
||||
**Риск:** endpoint-коллектор может стартовать без нужного deployment-конфига и работать с неверными/пустыми параметрами.
|
||||
|
||||
### 2) Нет строгой проверки HTTP-результата в file collector
|
||||
|
||||
В `file-operations-collector.ps1` POST выполняется через `HttpClient`, но код ответа не валидируется (`IsSuccessStatusCode` не проверяется), ошибки частично только логируются.
|
||||
|
||||
**Риск:** «тихая» потеря telemetry при 4xx/5xx.
|
||||
|
||||
### 3) Watcher не снимает event subscriptions явно
|
||||
|
||||
Есть `Register-ObjectEvent`, но в `finally` disposal только watcher-объектов; отписка событий (`Unregister-Event`) явно не делается.
|
||||
|
||||
**Риск:** при рестартах/долгой работе возможно накопление подписок в сессии.
|
||||
|
||||
### 4) Screenshot/GUI-зависимость для enforcement
|
||||
|
||||
`Capture-IncidentScreenshot` и balloon notification завязаны на `System.Windows.Forms/System.Drawing`.
|
||||
|
||||
**Риск:** в non-interactive / service context часть enforcement UX может не работать (событие уйдёт, но скриншот/уведомление может не сформироваться).
|
||||
|
||||
---
|
||||
|
||||
## Рекомендации (приоритет)
|
||||
|
||||
1. **P1:** выровнять дефолтный `ConfigPath` в `dlp-endpoint-signals-collector.ps1` на `C:\ProgramData\AWatch-rus\deployment-config.json`.
|
||||
2. **P1:** добавить проверку `response.IsSuccessStatusCode` в `file-operations-collector.ps1` и логировать body/status при ошибках.
|
||||
3. **P2:** сохранить subscription-объекты `Register-ObjectEvent` и делать `Unregister-Event` в `finally`.
|
||||
4. **P2:** для enforcement/UI добавить fallback режим «headless» (только лог + heartbeat).
|
||||
|
||||
---
|
||||
|
||||
## Что не удалось проверить в текущей среде
|
||||
|
||||
В этом контейнере отсутствует `pwsh`, поэтому не выполнены:
|
||||
|
||||
- синтаксический parse всех `*.ps1/*.psm1` через PowerShell parser;
|
||||
- `Test-ModuleManifest`;
|
||||
- smoke-run на Windows API (`Get-WinEvent`, `Get-Partition`, `Get-Disk`, `Set-Clipboard`, `Win32_PrintJob`).
|
||||
@@ -0,0 +1,183 @@
|
||||
# Стратегический анализ: AWatch-rus vs InfoWatch Traffic Monitor
|
||||
|
||||
## Текущие возможности AWatch-rus (что уже есть)
|
||||
|
||||
| Область | Реализовано |
|
||||
|---------|------------|
|
||||
| Activity tracking | `aw-watcher-afk`, `aw-watcher-window` — время активности, окна |
|
||||
| Browser monitoring | `browser-domains-native-collector.ps1` — URL/домены через UIAutomation |
|
||||
| DLP Phase 1 | Rule-based политики, incident bucket, cooldown/dedup |
|
||||
| DLP Phase 2 | USB/print/clipboard мониторинг (`dlp-endpoint-signals-collector.ps1`), incident screenshot |
|
||||
| File operations | Прототип `file-operations-collector.ps1` (create/delete/rename/archive) |
|
||||
| Worktime | Session-level presence (`worktime-session-collector.ps1`) |
|
||||
| Network | pfSense poller (firewall events → AW bucket) |
|
||||
| Aggregation | `aggregate_dlp_events.py` — SQLite/PostgreSQL centralized store |
|
||||
| UI | Русификация WebUI + DLP incident review panel в `aw-ru-patch.js` |
|
||||
| Deployment | Ansible + PowerShell ensemble + InnoSetup + Proxmox LXC |
|
||||
| Linux | Remote worker, console/SSH logger, web category logger |
|
||||
|
||||
## Ключевые разрывы до InfoWatch TM уровня
|
||||
|
||||
### 🔴 Критические (без них это не DLP, а мониторинг)
|
||||
|
||||
#### 1. Блокировка в реальном времени (Enforcement)
|
||||
**InfoWatch**: Перехватывает и блокирует отправку до завершения — email не уйдёт, файл на USB не запишется, печать не пройдёт.
|
||||
**AWatch-rus**: Только detect + log. Нет inline-перехвата трафика, нет file system filter driver.
|
||||
|
||||
**Что делать:**
|
||||
- **USB write-block** — реализуемо через Group Policy + PowerShell enforcement (`Set-StoragePolicy`, device lockdown via WMI). Не требует kernel driver.
|
||||
- **Print block** — перехват через Print Spooler event (307) + `Cancel-PrintJob`. Уже есть event monitoring — нужен только enforcement шаг.
|
||||
- **Clipboard block** — `SetClipboardData` hook через .NET или `Clear-Clipboard` при policy violation. Рискованно по UX, но возможно.
|
||||
- **Email/web block** — требует MITM proxy (аналог InfoWatch ICAP gateway). Реалистичнее начать с email-выгрузки через Exchange Journal/Transport Rule → анализ → карантин, чем строить inline proxy.
|
||||
- **Приоритет**: USB write-block + print cancel — быстрый win, 2-3 недели.
|
||||
|
||||
#### 2. Email перехват
|
||||
**InfoWatch**: SMTP/MAPI/IMAP — полный контроль корпоративной почты, включая вложения, тело, получатели.
|
||||
**AWatch-rus**: Отсутствует.
|
||||
|
||||
**Что делать:**
|
||||
- **Exchange/M365**: Transport Rule с Journal → Python парсер EML → событие в AW bucket `aw-email-monitor_<host>`.
|
||||
- **On-prem SMTP**: Milter на Postfix/Sendmail (Python, 200-300 строк).
|
||||
- **MVP**: Только metadata (from/to/subject/attachment names/sizes), без content inspection. Потом добавить content analysis.
|
||||
- **Приоритет**: Высокий — email это #1 канал утечки в enterprise.
|
||||
|
||||
#### 3. Контент-анализ (Content Inspection)
|
||||
**InfoWatch**: Лингвистический анализ, ML-классификация, OCR, document fingerprinting, digital watermarks, EDM (Exact Data Matching).
|
||||
**AWatch-rus**: Только regex-matching в DLP policy rules.
|
||||
|
||||
**Что делать поэтапно:**
|
||||
- **Phase A**: Словарные пакеты (ПДн по 152-ФЗ: ИНН, СНИЛС, паспорт, банковские реквизиты) — regex + Luhn/checksum validation. 1-2 недели.
|
||||
- **Phase B**: Keyword/weighted scoring — JSON pack с весами, threshold для incident. 1 неделя.
|
||||
- **Phase C**: OCR pipeline — Tesseract + screenshot analysis. Скриншоты уже снимаются! Нужен только OCR + content check step. 2-3 недели.
|
||||
- **Phase D**: Document fingerprinting — SimHash/MinHash для корпоративных шаблонов. Server-side Python service. 3-4 недели.
|
||||
- **Phase E**: ML-классификация — fine-tuned модель на корпоративных документах. Долгосрочно.
|
||||
|
||||
### 🟡 Важные (отличают серьёзный продукт от прототипа)
|
||||
|
||||
#### 4. Мессенджеры
|
||||
**InfoWatch**: Skype, Telegram, WhatsApp, VK, Jabber, MS Teams.
|
||||
**AWatch-rus**: Отсутствует.
|
||||
|
||||
**Что делать:**
|
||||
- Перехват через `aw-watcher-window` + title parsing уже частично даёт metadata (видно, что пользователь в Telegram).
|
||||
- Глубокий перехват текста мессенджеров требует accessibility API или memory scraping — сложно и ломко.
|
||||
- Реалистичнее: интеграция с корпоративными мессенджерами через API (MS Teams webhook, Mattermost API).
|
||||
- **MVP**: title-based detection "пользователь X общался в Telegram 2 часа" — уже почти есть.
|
||||
|
||||
#### 5. Облачные хранилища
|
||||
**InfoWatch**: Контроль загрузки в DropBox, Google Drive, OneDrive, Яндекс.Диск.
|
||||
**AWatch-rus**: Отсутствует.
|
||||
|
||||
**Что делать:**
|
||||
- Browser-based detection: URL matching `drive.google.com/upload`, `disk.yandex.ru` — расширить `browser-domains-native-collector.ps1` правилами.
|
||||
- Sync client detection: мониторинг процессов OneDrive/GoogleDrive + file watcher в sync-каталогах.
|
||||
- **MVP**: Alert "файл был загружен в облако" через browser URL + file operation correlation. 1-2 недели.
|
||||
|
||||
#### 6. SIEM/SOAR интеграция
|
||||
**InfoWatch**: CEF/syslog/API export, 75+ интеграций.
|
||||
**AWatch-rus**: SQLite/PostgreSQL aggregator — foundation есть, но нет стандартных connectors.
|
||||
|
||||
**Что делать:**
|
||||
- **CEF/Syslog exporter** — из `aggregate_dlp_events.py` в CEF format → rsyslog/Graylog/ELK. Python, 1-2 дня.
|
||||
- **Webhook/HTTP callback** — при incident severity=high → POST в Teams/Telegram/PagerDuty. 1 день.
|
||||
- **Grafana dashboards** — PostgreSQL уже поддерживается. Нужны готовые dashboard JSON. 2-3 дня.
|
||||
- **Приоритет**: CEF exporter + Grafana — быстрые wins для демонстрации "enterprise-ready".
|
||||
|
||||
#### 7. RBAC и multi-tenant admin
|
||||
**InfoWatch**: Ролевая модель, SoD, multi-tenant console, делегирование по отделам.
|
||||
**AWatch-rus**: Single admin, нет ролей.
|
||||
|
||||
**Что делать:**
|
||||
- ActivityWatch REST API не имеет auth из коробки. Нужен reverse proxy (nginx) с auth layer.
|
||||
- **MVP**: Basic auth + nginx + IP whitelist. Достаточно для PoC.
|
||||
- **Target**: Keycloak/LDAP auth proxy → role-based API access. Серьёзная работа, 4-6 недель.
|
||||
|
||||
### 🟢 Стратегические преимущества AWatch-rus (где вы уже лучше)
|
||||
|
||||
1. **Open-source база** — нет vendor lock-in, полный контроль над кодом.
|
||||
2. **Лёгкий agent** — PowerShell collectors vs тяжёлый C++ agent InfoWatch (~200MB RAM).
|
||||
3. **Гибкая DLP policy** — JSON rules, программируемые на лету, без перекомпиляции.
|
||||
4. **Linux поддержка** — remote workers, SSH/console logging, web category — InfoWatch слабее в Linux.
|
||||
5. **pfSense интеграция** — network visibility через firewall API, уникальная фича.
|
||||
6. **Worktime tracking** — session-level presence, отработка с 1С (Grafana dashboards).
|
||||
|
||||
## Рекомендуемый стратегический roadmap
|
||||
|
||||
### Квартал 1 (ближайшие 3 месяца) — "Enforce & Detect"
|
||||
| # | Задача | Усилие | Влияние |
|
||||
|---|--------|--------|---------|
|
||||
| 1 | USB write-block (GPO + enforcement step) | 2 нед | Критическое |
|
||||
| 2 | Print job cancel при policy violation | 1 нед | Критическое |
|
||||
| 3 | ПДн словарный пакет (ИНН/СНИЛС/паспорт regex) | 2 нед | Высокое |
|
||||
| 4 | Email metadata collector (Exchange Journal) | 3 нед | Критическое |
|
||||
| 5 | CEF/Syslog exporter для SIEM | 3 дня | Высокое |
|
||||
| 6 | Grafana incident dashboards | 3 дня | Среднее |
|
||||
|
||||
### Квартал 2 — "Content & Cloud"
|
||||
| # | Задача | Усилие | Влияние |
|
||||
|---|--------|--------|---------|
|
||||
| 7 | Cloud storage upload detection | 2 нед | Высокое |
|
||||
| 8 | OCR pipeline (Tesseract + screenshot analysis) | 3 нед | Высокое |
|
||||
| 9 | Weighted keyword scoring engine | 1 нед | Среднее |
|
||||
| 10 | Webhook/callback при critical incidents | 2 дня | Среднее |
|
||||
| 11 | Clipboard enforcement (clear on violation) | 1 нед | Среднее |
|
||||
|
||||
### Квартал 3 — "Intelligence & Scale"
|
||||
| # | Задача | Усилие | Влияние |
|
||||
|---|--------|--------|---------|
|
||||
| 12 | Policy engine service (server-side, versioned) | 4 нед | Критическое |
|
||||
| 13 | Document fingerprinting (SimHash) | 3 нед | Высокое |
|
||||
| 14 | UEBA / risk scoring (anomaly detection) | 4 нед | Высокое |
|
||||
| 15 | RBAC auth proxy (Keycloak/LDAP) | 4 нед | Среднее |
|
||||
| 16 | Correlation engine (user+channel+object+time) | 3 нед | Высокое |
|
||||
|
||||
### Квартал 4 — "Enterprise"
|
||||
| # | Задача | Усилие | Влияние |
|
||||
|---|--------|--------|---------|
|
||||
| 17 | Case management (investigations) | 4 нед | Среднее |
|
||||
| 18 | Evidence chain / immutable audit log | 2 нед | Среднее |
|
||||
| 19 | Compliance report generator (152-ФЗ) | 3 нед | Высокое |
|
||||
| 20 | ML document classifier | 6+ нед | Высокое |
|
||||
|
||||
## Архитектурная рекомендация
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ AWatch-rus Server (Rust) │
|
||||
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
|
||||
│ │ AW API │ │ Policy │ │ Content │ │
|
||||
│ │ (buckets │ │ Engine │ │ Analysis │ │
|
||||
│ │ events) │ │ (Python │ │ Service │ │
|
||||
│ │ │ │ sidecar) │ │ (OCR/ML/ │ │
|
||||
│ │ │ │ │ │ fingerprint)│ │
|
||||
│ └────┬─────┘ └─────┬─────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────┴──────────────┴───────────────┴────────┐ │
|
||||
│ │ PostgreSQL / SQLite │ │
|
||||
│ └────┬──────────────┬───────────────┬────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────┴─────┐ ┌─────┴─────┐ ┌─────┴────────┐ │
|
||||
│ │ Grafana │ │ CEF/SIEM │ │ Webhook │ │
|
||||
│ │ Dash │ │ Export │ │ Alerts │ │
|
||||
│ └──────────┘ └───────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
▲ ▲ ▲
|
||||
┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴──────┐
|
||||
│ Windows │ │ Linux │ │ Network │
|
||||
│ Endpoint │ │ Endpoint │ │ (pfSense, │
|
||||
│ Collectors │ │ Watchers │ │ email │
|
||||
│ + Enforce │ │ + SSH log │ │ gateway) │
|
||||
└─────────────┘ └───────────┘ └────────────┘
|
||||
```
|
||||
|
||||
## Вывод
|
||||
|
||||
AWatch-rus уже покрывает ~25-30% функционала InfoWatch TM по каналам мониторинга. Критический разрыв — **отсутствие блокировки** (enforcement) и **контент-анализа**. Без этих двух компонентов продукт классифицируется как "мониторинг активности", а не "DLP".
|
||||
|
||||
Реалистичный путь к конкурентоспособности за 6-9 месяцев:
|
||||
1. Добавить enforcement (USB/print/clipboard block) — переводит из "мониторинг" в "prevention"
|
||||
2. Добавить content inspection (regex packs + OCR) — переводит из "метаданные" в "DLP"
|
||||
3. Добавить email канал — закрывает #1 канал утечки
|
||||
4. Добавить SIEM/Grafana export — делает продукт "enterprise-visible"
|
||||
|
||||
Сильная сторона проекта — **гибкость и лёгкость агента**. InfoWatch agent — тяжёлый C++ monolith. AWatch-rus collectors — лёгкие PowerShell/Python скрипты, которые можно быстро адаптировать. Это стратегическое преимущество для SMB/mid-market, где InfoWatch слишком дорог и тяжёл.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Архитектура системы
|
||||
|
||||
## Обзор
|
||||
|
||||
ActivityWatch-Russian состоит из нескольких слоев:
|
||||
|
||||
```
|
||||
Windows Clients (сбор данных)
|
||||
↓ HTTP API
|
||||
Linux Server (хранение и обработка)
|
||||
↓ TCP
|
||||
Integration Layer (интеграции)
|
||||
↓ HTTP/TCP
|
||||
Monitoring Stack (визуализация)
|
||||
```
|
||||
|
||||
## Слои архитектуры
|
||||
|
||||
### 1. Windows Clients - Сбор данных
|
||||
- **DLP Endpoint Collector** - мониторинг clipboard, печати, USB
|
||||
- **Browser Domains Collector** - мониторинг браузеров
|
||||
- **Email Outbound Collector** - мониторинг почты
|
||||
- **Worktime Session Collector** - учет рабочего времени
|
||||
|
||||
### 2. Linux Server - Хранение и обработка
|
||||
- **ActivityWatch Server** - основной сервер (Rust)
|
||||
- **PostgreSQL Database** - хранилище данных
|
||||
- **WebUI with RU Patches** - веб-интерфейс с русификацией
|
||||
|
||||
### 3. Integration Layer - Интеграции
|
||||
- **pfSense Poller** - сбор логов firewall
|
||||
- **DLP Aggregation Scripts** - агрегация DLP событий
|
||||
- **Prometheus Exporter** - экспорт метрик
|
||||
|
||||
### 4. Monitoring Stack - Мониторинг
|
||||
- **Prometheus** - сбор метрик
|
||||
- **Grafana** - визуализация
|
||||
- **SQL Exporter** - прямые SQL запросы
|
||||
|
||||
## Потоки данных
|
||||
|
||||
### DLP инцидент
|
||||
```
|
||||
User → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
|
||||
```
|
||||
|
||||
### Браузер мониторинг
|
||||
```
|
||||
Browser → Browser Collector → Категоризация → AW Server → WebUI
|
||||
```
|
||||
|
||||
### Метрики
|
||||
```
|
||||
AW Server → Exporter → Prometheus → Grafana
|
||||
```
|
||||
|
||||
## Порты
|
||||
|
||||
| Компонент | Порт | Протокол |
|
||||
|-----------|------|----------|
|
||||
| ActivityWatch API | 5600 | HTTP |
|
||||
| ActivityWatch WebSocket | 5666 | WebSocket |
|
||||
| PostgreSQL | 5432 | TCP |
|
||||
| Prometheus | 9090 | HTTP |
|
||||
| Grafana | 3000 | HTTP |
|
||||
| Prometheus Exporter | 9398 | HTTP |
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Компоненты системы](Components) - детальное описание компонентов
|
||||
- [Интерактивная карта](Interactive-Map) - визуальная схема связей
|
||||
@@ -0,0 +1,145 @@
|
||||
# Browser Domains Monitoring
|
||||
|
||||
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
|
||||
|
||||
## Обзор
|
||||
|
||||
Browser Domains Collector отслеживает:
|
||||
- Посещаемые веб-сайты
|
||||
- Категоризацию доменов
|
||||
- DLP проверку запрещенных сайтов
|
||||
- Скриншоты при нарушениях
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
Browser → Window Detection → URL Extraction → Domain Parsing → Categorization → DLP Check → ActivityWatch
|
||||
```
|
||||
|
||||
## Функции
|
||||
|
||||
### Domain Detection
|
||||
- Определение активного окна браузера
|
||||
- Извлечение URL из заголовка окна
|
||||
- Парсинг домена из URL
|
||||
- Определение корневого домена
|
||||
|
||||
### Categorization
|
||||
- Кастомные правила категорий
|
||||
- Web Category API (опционально)
|
||||
- Кэширование результатов
|
||||
- Автоопределение категорий
|
||||
|
||||
### DLP Checking
|
||||
- Проверка по спискам доменов
|
||||
- DLP правила для доменов
|
||||
- Временные окна
|
||||
- Блокировка нарушений
|
||||
|
||||
## Поддерживаемые браузеры
|
||||
|
||||
- Google Chrome
|
||||
- Mozilla Firefox
|
||||
- Microsoft Edge
|
||||
- Opera
|
||||
- Яндекс.Браузер
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Custom Category Rules
|
||||
```json
|
||||
{
|
||||
"custom_categories": {
|
||||
"social_media": [
|
||||
"*.facebook.com",
|
||||
"*.twitter.com",
|
||||
"*.instagram.com"
|
||||
],
|
||||
"news": [
|
||||
"*.news.com",
|
||||
"*.media.com"
|
||||
],
|
||||
"blocked": [
|
||||
"*.malware.com",
|
||||
"*.phishing.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Domain Rules
|
||||
```json
|
||||
{
|
||||
"domain_rules": [
|
||||
{
|
||||
"pattern": "*gambling*",
|
||||
"category": "gambling",
|
||||
"action": "alert",
|
||||
"severity": "medium"
|
||||
},
|
||||
{
|
||||
"pattern": "*adult*",
|
||||
"category": "adult",
|
||||
"action": "block",
|
||||
"severity": "high"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## События
|
||||
|
||||
### Browsing Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "browsing",
|
||||
"data": {
|
||||
"url": "https://www.example.com/page",
|
||||
"domain": "example.com",
|
||||
"root_domain": "example.com",
|
||||
"category": "technology",
|
||||
"browser": "chrome.exe",
|
||||
"title": "Example Page Title",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Incident Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "dlp_incident",
|
||||
"source": "browser_domain",
|
||||
"rule_id": "blocked_domains",
|
||||
"severity": "high",
|
||||
"data": {
|
||||
"url": "https://blocked.com",
|
||||
"domain": "blocked.com",
|
||||
"category": "malicious",
|
||||
"screenshot": "path/to/screenshot.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- Кэширование категорий (TTL 24h)
|
||||
- Batch запросы к category API
|
||||
- Debouncing быстрых переходов
|
||||
- Асинхронная отправка событий
|
||||
|
||||
## Мониторинг
|
||||
|
||||
- Количество уникальных доменов
|
||||
- Распределение по категориям
|
||||
- Частота DLP инцидентов
|
||||
- Кэш hit rate
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/browser-domains-monitoring.md)
|
||||
- [Категоризация сайтов](Web-Categorization)
|
||||
@@ -0,0 +1,108 @@
|
||||
# Компоненты системы
|
||||
|
||||
## Windows Collectors
|
||||
|
||||
### DLP Endpoint Collector
|
||||
Мониторинг конечных точек для обнаружения утечек данных.
|
||||
|
||||
**Функции:**
|
||||
- Clipboard мониторинг
|
||||
- Print job мониторинг
|
||||
- USB write блокировка
|
||||
- DLP правило evaluation
|
||||
- Скриншоты при инцидентах
|
||||
|
||||
**Файл:** `windows/dlp-endpoint-signals-collector.ps1`
|
||||
|
||||
### Browser Domains Collector
|
||||
Отслеживание посещаемых доменов с категоризацией.
|
||||
|
||||
**Функции:**
|
||||
- Определение домена из URL
|
||||
- Категоризация сайтов
|
||||
- DLP проверка доменов
|
||||
- Скриншоты при нарушениях
|
||||
|
||||
**Файл:** `windows/browser-domains-native-collector.ps1`
|
||||
|
||||
### Email Outbound Collector
|
||||
Мониторинг исходящей почты.
|
||||
|
||||
**Функции:**
|
||||
- Outlook Sent Items мониторинг
|
||||
- SMTP соединения
|
||||
- Email правило evaluation
|
||||
- Блокировка нарушений
|
||||
|
||||
**Файл:** `windows/email-outbound-collector.ps1`
|
||||
|
||||
### Worktime Session Collector
|
||||
Учет рабочего времени.
|
||||
|
||||
**Функции:**
|
||||
- Определение сессий
|
||||
- Учет перерывов
|
||||
- Агрегация по дням
|
||||
|
||||
**Файл:** `windows/worktime-session-collector.ps1`
|
||||
|
||||
## Server Components
|
||||
|
||||
### ActivityWatch Server
|
||||
Основной сервер для приема и хранения событий.
|
||||
|
||||
**Технологии:** Rust
|
||||
**API:** HTTP на порту 5600
|
||||
|
||||
### PostgreSQL Database
|
||||
Основное хранилище данных.
|
||||
|
||||
**Таблицы:**
|
||||
- `dlp_events` - DLP инциденты
|
||||
- `aggregation_state` - состояние агрегации
|
||||
- `dlp_statistics` - статистика
|
||||
|
||||
### WebUI with RU Patches
|
||||
Веб-интерфейс с русской локализацией.
|
||||
|
||||
**Файлы:** `aw-server/aw-ru-patch.js`, `aw-server/aw-sw-cleanup.js`
|
||||
|
||||
## Integration Components
|
||||
|
||||
### pfSense Poller
|
||||
Сбор логов с pfSense firewall.
|
||||
|
||||
**Файл:** `pfsense/pfsense-aw-poller.py`
|
||||
|
||||
### DLP Aggregation Scripts
|
||||
Агрегация DLP событий в PostgreSQL.
|
||||
|
||||
**Файл:** `scripts/aggregate_dlp_events.py`
|
||||
|
||||
### Prometheus Exporter
|
||||
Экспорт метрик в формате Prometheus.
|
||||
|
||||
**Файл:** `grafana-1c/sql-exporter/collectors/aw_activitywatch.py`
|
||||
|
||||
## Monitoring Components
|
||||
|
||||
### Prometheus
|
||||
Сбор и хранение метрик.
|
||||
|
||||
**Порт:** 9090
|
||||
|
||||
### Grafana
|
||||
Визуализация метрик и дашборды.
|
||||
|
||||
**Порт:** 3000
|
||||
|
||||
### SQL Exporter
|
||||
Прямые SQL запросы к PostgreSQL.
|
||||
|
||||
**Порт:** 9398
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - детально о DLP коллекторе
|
||||
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - детально о браузерном коллекторе
|
||||
- [WebUI Русификация](WebUI-Russian-Patches) - детально о патчах интерфейса
|
||||
@@ -0,0 +1,124 @@
|
||||
# DLP Endpoint Monitoring
|
||||
|
||||
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
|
||||
|
||||
## Обзор
|
||||
|
||||
DLP Endpoint Collector отслеживает:
|
||||
- **Clipboard** - буфер обмена
|
||||
- **Print** - задания на печать
|
||||
- **USB** - запись на USB накопители
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
User Activity → DLP Collector → Rule Evaluation → Enforcement → ActivityWatch
|
||||
```
|
||||
|
||||
## Функции
|
||||
|
||||
### Clipboard Monitoring
|
||||
- Перехват clipboard событий
|
||||
- Проверка по паттернам (credit cards, passport data, etc.)
|
||||
- Блокировка копирования
|
||||
- Скриншот при нарушении
|
||||
|
||||
### Print Monitoring
|
||||
- Перехват print job событий
|
||||
- Проверка принтеров и документов
|
||||
- Блокировка печати
|
||||
- Логирование попыток
|
||||
|
||||
### USB Monitoring
|
||||
- Обнаружение USB устройств
|
||||
- Блокировка записи
|
||||
- Логирование подключений
|
||||
- Политики по device ID
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### DLP Policy
|
||||
```json
|
||||
{
|
||||
"clipboard_rules": [
|
||||
{
|
||||
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
|
||||
"description": "Credit card numbers",
|
||||
"severity": "high",
|
||||
"action": "block"
|
||||
}
|
||||
],
|
||||
"print_rules": [
|
||||
{
|
||||
"printer_match": "*",
|
||||
"document_keywords": ["confidential", "secret"],
|
||||
"action": "block"
|
||||
}
|
||||
],
|
||||
"usb_rules": [
|
||||
{
|
||||
"device_id": "*",
|
||||
"action": "block_write"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment Config
|
||||
```json
|
||||
{
|
||||
"aw_server_url": "http://aw-server:5600",
|
||||
"bucket_prefix": "aw-watcher-dlp-endpoint",
|
||||
"heartbeat_interval": 60,
|
||||
"screenshot_on_incident": true,
|
||||
"enforcement_enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
## Установка
|
||||
|
||||
```powershell
|
||||
# Копирование коллектора
|
||||
Copy-Item windows/dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
|
||||
|
||||
# Настройка scheduled task
|
||||
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
|
||||
```
|
||||
|
||||
## События
|
||||
|
||||
### DLP Incident Event
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"type": "dlp_incident",
|
||||
"source": "clipboard",
|
||||
"rule_id": "credit_card_pattern",
|
||||
"severity": "high",
|
||||
"data": {
|
||||
"matched_text": "****-****-****-1234",
|
||||
"user": "user1",
|
||||
"host": "WORKSTATION01",
|
||||
"screenshot": "path/to/screenshot.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Требования
|
||||
|
||||
- Windows 10/11
|
||||
- PowerShell 5.1+
|
||||
- ActivityWatch installed
|
||||
- Административные права (для enforcement)
|
||||
|
||||
## Мониторинг
|
||||
|
||||
- Количество инцидентов по типам
|
||||
- Частота срабатываний правил
|
||||
- Успешность enforcement действий
|
||||
- Heartbeat статус
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/dlp-endpoint-monitoring.md)
|
||||
- [DLP Правила](DLP-Rules)
|
||||
@@ -0,0 +1,79 @@
|
||||
# ActivityWatch-Russian Documentation
|
||||
|
||||
Добро пожаловать в документацию ActivityWatch-Russian - корпоративной системы мониторинга активности на базе ActivityWatch с русификацией и DLP функциями.
|
||||
|
||||
## 📚 Содержание
|
||||
|
||||
### Архитектура
|
||||
- [Обзор архитектуры](Architecture) - высокоуровневая архитектура системы
|
||||
- [Компоненты системы](Components) - описание всех компонентов
|
||||
- [Интерактивная карта](Interactive-Map) - визуальная карта связей
|
||||
|
||||
### Компоненты
|
||||
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB
|
||||
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - мониторинг браузеров
|
||||
- [Email Outbound Monitoring](Email-Outbound-Monitoring) - мониторинг почты
|
||||
- [WebUI Русификация](WebUI-Russian-Patches) - патчи интерфейса
|
||||
- [DLP Агрегация](DLP-Aggregation) - обработка DLP событий
|
||||
- [Prometheus Exporter](Prometheus-Exporter) - метрики для мониторинга
|
||||
|
||||
### Развертывание
|
||||
- [Установка на Windows](Windows-Installation) - установка коллекторов
|
||||
- [Настройка сервера](Server-Setup) - настройка Linux сервера
|
||||
- [Grafana + Prometheus](Monitoring-Setup) - мониторинг стек
|
||||
|
||||
### Конфигурация
|
||||
- [DLP Правила](DLP-Rules) - настройка DLP политик
|
||||
- [Категоризация сайтов](Web-Categorization) - настройка категорий
|
||||
- [Группы хостов](Host-Groups) - управление группами
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### Минимальная конфигурация
|
||||
```bash
|
||||
# 1. Установка на Windows workstation
|
||||
.\windows\deploy-domain-users.ps1
|
||||
|
||||
# 2. Запуск ActivityWatch Server
|
||||
./aw-server/aw-server
|
||||
|
||||
# 3. Применение RU патчей
|
||||
node aw-server/aw-ru-patch.js
|
||||
```
|
||||
|
||||
### Полная конфигурация
|
||||
```bash
|
||||
# 1. Развертывание на Windows
|
||||
.\windows\deploy-domain-users.ps1
|
||||
|
||||
# 2. Настройка сервера
|
||||
cd ansible
|
||||
ansible-playbook server-setup.yml
|
||||
|
||||
# 3. Запуск мониторинга стека
|
||||
cd ../grafana-1c
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Агрегация DLP событий
|
||||
python3 scripts/aggregate_dlp_events.py
|
||||
```
|
||||
|
||||
## 📊 Обзор системы
|
||||
|
||||
ActivityWatch-Russian - это корпоративная система мониторинга активности пользователей с:
|
||||
|
||||
- **DLP мониторинг** - clipboard, печать, USB, браузеры, email
|
||||
- **Русификация** - полный перевод интерфейса на русский
|
||||
- **Аналитика** - агрегация данных и отчеты
|
||||
- **Мониторинг** - Prometheus + Grafana дашборды
|
||||
- **Автоматизация** - Ansible деплой на Windows и Linux
|
||||
|
||||
## 🔗 Ссылки
|
||||
|
||||
- [GitHub Repository](https://github.com/igor04091968/AWatch-rus)
|
||||
- [ActivityWatch Official](https://activitywatch.net/)
|
||||
- [Примеры конфигураций](https://github.com/igor04091968/AWatch-rus/tree/main/grafana-1c)
|
||||
|
||||
## 📝 Поддержка
|
||||
|
||||
Для вопросов и предложений используйте [Issues](https://github.com/igor04091968/AWatch-rus/issues).
|
||||
@@ -0,0 +1,78 @@
|
||||
# Интерактивная карта архитектуры
|
||||
|
||||
Для визуального исследования связей между компонентами используйте интерактивную HTML карту.
|
||||
|
||||
## Как использовать
|
||||
|
||||
### Локально
|
||||
```bash
|
||||
# Открыть в браузере
|
||||
xdg-open docs/diagrams/architecture-map.html
|
||||
|
||||
# Или
|
||||
file:///path/to/AWatch-rus/docs/diagrams/architecture-map.html
|
||||
```
|
||||
|
||||
### С GitHub
|
||||
1. Скачайте файл: [architecture-map.html](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/architecture-map.html)
|
||||
2. Откройте в браузере
|
||||
|
||||
## Функции карты
|
||||
|
||||
### Визуальные компоненты
|
||||
- **Windows Clients** (красный) - коллекторы данных
|
||||
- **Linux Server** (бирюзовый) - сервер и БД
|
||||
- **Integration** (желтый) - интеграции
|
||||
- **Monitoring** (зеленый) - мониторинг стек
|
||||
- **External** (фиолетовый) - внешние системы
|
||||
|
||||
### Интерактивность
|
||||
- **Клик по компоненту** - показывает детальную информацию
|
||||
- **Подсветка связей** - выделяет связанные компоненты
|
||||
- **Поиск** - быстрый поиск по названию
|
||||
- **Потоки данных** - визуализация процесса
|
||||
|
||||
### Информация о компоненте
|
||||
При клике на компонент показывается:
|
||||
- Описание
|
||||
- Связи с другими компонентами
|
||||
- Потоки данных
|
||||
- Порты и протоколы
|
||||
|
||||
## Слои архитектуры
|
||||
|
||||
### 1. Windows Clients
|
||||
- DLP Endpoint Collector
|
||||
- Browser Domains Collector
|
||||
- Email Outbound Collector
|
||||
- Worktime Session Collector
|
||||
|
||||
### 2. Linux Server
|
||||
- ActivityWatch Server
|
||||
- PostgreSQL Database
|
||||
- WebUI with RU Patches
|
||||
|
||||
### 3. Integration Layer
|
||||
- pfSense Poller
|
||||
- DLP Aggregation Scripts
|
||||
- Prometheus Exporter
|
||||
|
||||
### 4. Monitoring Stack
|
||||
- Prometheus
|
||||
- Grafana
|
||||
- SQL Exporter
|
||||
|
||||
### 5. External Systems
|
||||
- pfSense Firewall
|
||||
- Domain Controller
|
||||
|
||||
## Альтернативные визуализации
|
||||
|
||||
### ASCII схема
|
||||
Для быстрого понимания: [architecture-simple.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-simple.md)
|
||||
|
||||
### Mermaid диаграммы
|
||||
Для технических деталей: [architecture-diagram.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-diagram.md)
|
||||
|
||||
### Graphify knowledge graph
|
||||
Для анализа кода: [graphify-out/index.html](https://github.com/igor04091968/AWatch-rus/blob/main/graphify-out/index.html)
|
||||
@@ -0,0 +1,292 @@
|
||||
# Monitoring Setup
|
||||
|
||||
Настройка Prometheus + Grafana стека для мониторинга ActivityWatch.
|
||||
|
||||
## Требования
|
||||
|
||||
- Docker & Docker Compose
|
||||
- 2GB+ RAM
|
||||
- 20GB+ disk space
|
||||
|
||||
## Установка через Docker Compose
|
||||
|
||||
```bash
|
||||
cd grafana-1c
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Компоненты
|
||||
|
||||
### Prometheus
|
||||
Сбор и хранение метрик.
|
||||
|
||||
**Порт:** 9090
|
||||
**Конфиг:** `prometheus/prometheus.yml`
|
||||
|
||||
### Grafana
|
||||
Визуализация метрик и дашборды.
|
||||
|
||||
**Порт:** 3000
|
||||
**Конфиг:** `grafana/provisioning/`
|
||||
|
||||
### SQL Exporter
|
||||
Прямые SQL запросы к PostgreSQL.
|
||||
|
||||
**Порт:** 9398
|
||||
**Конфиг:** `sql-exporter/`
|
||||
|
||||
### AW Exporter
|
||||
Экспорт метрик ActivityWatch.
|
||||
|
||||
**Порт:** 9398
|
||||
**Файл:** `sql-exporter/collectors/aw_activitywatch.py`
|
||||
|
||||
## Конфигурация Prometheus
|
||||
|
||||
```yaml
|
||||
# prometheus/prometheus.yml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'activitywatch'
|
||||
static_configs:
|
||||
- targets: ['aw-exporter:9398']
|
||||
scrape_interval: 60s
|
||||
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
```
|
||||
|
||||
## Конфигурация Grafana
|
||||
|
||||
### Datasources
|
||||
|
||||
```yaml
|
||||
# grafana/provisioning/datasources/prometheus.yml
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
```
|
||||
|
||||
```yaml
|
||||
# grafana/provisioning/datasources/postgres.yml
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: PostgreSQL
|
||||
type: postgres
|
||||
access: proxy
|
||||
url: postgres:5432
|
||||
database: activitywatch
|
||||
user: aw
|
||||
password: aw_password
|
||||
```
|
||||
|
||||
### Dashboards
|
||||
|
||||
```yaml
|
||||
# grafana/provisioning/dashboards/dashboards.yml
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'ActivityWatch'
|
||||
orgId: 1
|
||||
folder: 'ActivityWatch'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 10
|
||||
allowUiUpdates: true
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
```
|
||||
|
||||
## Дашборды
|
||||
|
||||
### ActivityWatch Overview
|
||||
- Активные хосты
|
||||
- События по buckets
|
||||
- Скорость поступления событий
|
||||
- Статус коллекторов
|
||||
|
||||
**Файл:** `grafana/grafana/dashboards/aw_overview.json`
|
||||
|
||||
### DLP Incidents
|
||||
- Количество инцидентов по типам
|
||||
- Инциденты по пользователям
|
||||
- Инциденты по серьезности
|
||||
- Временные тренды
|
||||
|
||||
### Browser Monitoring
|
||||
- Посещаемые домены
|
||||
- Категории сайтов
|
||||
- DLP нарушения
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
# Запуск всех сервисов
|
||||
docker-compose up -d
|
||||
|
||||
# Проверка статуса
|
||||
docker-compose ps
|
||||
|
||||
# Просмотр логов
|
||||
docker-compose logs -f
|
||||
|
||||
# Остановка
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Доступ
|
||||
|
||||
- **Grafana:** http://localhost:3000 (admin/admin)
|
||||
- **Prometheus:** http://localhost:9090
|
||||
- **AW Exporter:** http://localhost:9398/metrics
|
||||
|
||||
## Настройка алертов
|
||||
|
||||
### Prometheus Alerting Rules
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: activitywatch_alerts
|
||||
rules:
|
||||
- alert: AWCollectorDown
|
||||
expr: aw_host_active == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "ActivityWatch collector down on {{ $labels.host }}"
|
||||
|
||||
- alert: AWExporterDown
|
||||
expr: aw_exporter_up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "ActivityWatch exporter is down"
|
||||
```
|
||||
|
||||
### Grafana Notification Channels
|
||||
|
||||
1. Откройте Grafana
|
||||
2. Configuration → Alerting → Notification channels
|
||||
3. Add new channel (Email, Slack, etc.)
|
||||
4. Настройте правила алертов в дашбордах
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Ключевые метрики
|
||||
|
||||
**ActivityWatch:**
|
||||
- `aw_bucket_events_total` - общее количество событий
|
||||
- `aw_host_active` - активность хостов
|
||||
- `aw_exporter_scrape_duration_seconds` - время scrape
|
||||
|
||||
**Prometheus:**
|
||||
- `up` - статус целей
|
||||
- `scrape_duration_seconds` - время сбора
|
||||
|
||||
**Grafana:**
|
||||
- `grafana_statistic` - статистика Grafana
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Prometheus
|
||||
curl http://localhost:9090/-/healthy
|
||||
|
||||
# Grafana
|
||||
curl http://localhost:3000/api/health
|
||||
|
||||
# AW Exporter
|
||||
curl http://localhost:9398/health
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
```bash
|
||||
# Backup Grafana dashboards
|
||||
docker exec grafana grafana-cli admin export-dashboard > dashboards_backup.json
|
||||
|
||||
# Backup Prometheus data
|
||||
docker exec prometheus tar -czf /tmp/prometheus_data.tar.gz /prometheus
|
||||
docker cp prometheus:/tmp/prometheus_data.tar.gz ./backup/
|
||||
```
|
||||
|
||||
## Устранение проблем
|
||||
|
||||
### Grafana не доступна
|
||||
```bash
|
||||
# Проверьте контейнер
|
||||
docker-compose ps grafana
|
||||
|
||||
# Проверьте логи
|
||||
docker-compose logs grafana
|
||||
|
||||
# Перезапустите
|
||||
docker-compose restart grafana
|
||||
```
|
||||
|
||||
### Prometheus не собирает метрики
|
||||
```bash
|
||||
# Проверьте конфиг
|
||||
docker exec prometheus promtool check config /etc/prometheus/prometheus.yml
|
||||
|
||||
# Проверьте цели
|
||||
curl http://localhost:9090/api/v1/targets
|
||||
|
||||
# Проверьте логи
|
||||
docker-compose logs prometheus
|
||||
```
|
||||
|
||||
### AW Exporter не работает
|
||||
```bash
|
||||
# Проверьте соединение с AW Server
|
||||
docker exec aw-exporter curl http://aw-server:5600/api/0/buckets
|
||||
|
||||
# Проверьте логи
|
||||
docker-compose logs aw-exporter
|
||||
|
||||
# Проверьте метрики
|
||||
curl http://localhost:9398/metrics
|
||||
```
|
||||
|
||||
## Производительность
|
||||
|
||||
### Оптимизация Prometheus
|
||||
|
||||
```yaml
|
||||
# prometheus/prometheus.yml
|
||||
global:
|
||||
scrape_interval: 30s # Увеличьте интервал
|
||||
evaluation_interval: 30s
|
||||
|
||||
storage:
|
||||
tsdb:
|
||||
retention.time: 30d # Уменьшите retention
|
||||
```
|
||||
|
||||
### Оптимизация Grafana
|
||||
|
||||
```bash
|
||||
# Увеличьте память
|
||||
# docker-compose.yml
|
||||
grafana:
|
||||
environment:
|
||||
- GF_INSTALL_PLUGINS=
|
||||
- GF_SERVER_ROOT_URL=http://localhost:3000
|
||||
- GF_ANALYTICS_ENABLED=false
|
||||
```
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Prometheus Exporter](Prometheus-Exporter)
|
||||
- [Компоненты](Components)
|
||||
@@ -0,0 +1,248 @@
|
||||
# Настройка сервера
|
||||
|
||||
Настройка Linux сервера для ActivityWatch-Russian.
|
||||
|
||||
## Требования
|
||||
|
||||
- Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)
|
||||
- Python 3.8+
|
||||
- PostgreSQL 12+
|
||||
- 4GB+ RAM
|
||||
- 50GB+ disk space
|
||||
|
||||
## Установка через Ansible
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook server-setup.yml
|
||||
```
|
||||
|
||||
## Ручная установка
|
||||
|
||||
### 1. Установка ActivityWatch Server
|
||||
|
||||
```bash
|
||||
# Скачайте binaries
|
||||
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.1/activitywatch-v0.13.1-linux-x86_64.zip
|
||||
|
||||
# Распакуйте
|
||||
unzip activitywatch-v0.13.1-linux-x86_64.zip
|
||||
cd activitywatch-v0.13.1-linux-x86_64
|
||||
|
||||
# Запустите сервер
|
||||
./aw-server
|
||||
```
|
||||
|
||||
### 2. Установка PostgreSQL
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo yum install postgresql-server postgresql-contrib
|
||||
sudo postgresql-setup initdb
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
|
||||
# Создайте базу данных
|
||||
sudo -u postgres psql
|
||||
CREATE DATABASE activitywatch;
|
||||
CREATE USER aw WITH PASSWORD 'your_password';
|
||||
GRANT ALL PRIVILEGES ON DATABASE activitywatch TO aw;
|
||||
\q
|
||||
```
|
||||
|
||||
### 3. Применение RU патчей
|
||||
|
||||
```bash
|
||||
# Скопируйте патчи
|
||||
cp aw-server/aw-ru-patch.js /path/to/aw-server/
|
||||
cp aw-server/aw-sw-cleanup.js /path/to/aw-server/
|
||||
|
||||
# Примените патчи
|
||||
cd /path/to/aw-server/
|
||||
node aw-ru-patch.js
|
||||
```
|
||||
|
||||
### 4. Настройка systemd service
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/aw-server.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=ActivityWatch Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aw
|
||||
WorkingDirectory=/opt/activitywatch
|
||||
ExecStart=/opt/activitywatch/aw-server
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl enable aw-server
|
||||
sudo systemctl start aw-server
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### ActivityWatch Config
|
||||
|
||||
```toml
|
||||
# /opt/activitywatch/server-config.toml
|
||||
[server]
|
||||
host = "0.0.0.0"
|
||||
port = 5600
|
||||
|
||||
[logging]
|
||||
level = "INFO"
|
||||
file = "/var/log/activitywatch/aw-server.log"
|
||||
```
|
||||
|
||||
### PostgreSQL Config
|
||||
|
||||
```bash
|
||||
# /etc/postgresql/12/main/postgresql.conf
|
||||
listen_addresses = '*'
|
||||
max_connections = 100
|
||||
shared_buffers = 256MB
|
||||
```
|
||||
|
||||
## Установка Python зависимостей
|
||||
|
||||
```bash
|
||||
# Создайте virtual environment
|
||||
python3 -m venv /opt/aw-venv
|
||||
source /opt/aw-venv/bin/activate
|
||||
|
||||
# Установите зависимости
|
||||
pip install psycopg2-binary requests prometheus_client
|
||||
```
|
||||
|
||||
## Настройка скриптов агрегации
|
||||
|
||||
```bash
|
||||
# Скопируйте скрипты
|
||||
cp scripts/aggregate_dlp_events.py /opt/activitywatch/
|
||||
|
||||
# Настройте cron job
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```cron
|
||||
*/5 * * * * /opt/aw-venv/bin/python /opt/activitywatch/aggregate_dlp_events.py
|
||||
```
|
||||
|
||||
## Firewall
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian (ufw)
|
||||
sudo ufw allow 5600/tcp
|
||||
sudo ufw allow 5432/tcp
|
||||
sudo ufw enable
|
||||
|
||||
# CentOS/RHEL (firewalld)
|
||||
sudo firewall-cmd --permanent --add-port=5600/tcp
|
||||
sudo firewall-cmd --permanent --add-port=5432/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
# Проверка ActivityWatch Server
|
||||
curl http://localhost:5600/api/0/buckets
|
||||
|
||||
# Проверка PostgreSQL
|
||||
sudo -u postgres psql -c "SELECT version();"
|
||||
|
||||
# Проверка логов
|
||||
sudo journalctl -u aw-server -f
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
```bash
|
||||
# Проверка статуса
|
||||
sudo systemctl status aw-server
|
||||
|
||||
# Проверка ресурсов
|
||||
htop
|
||||
|
||||
# Проверка диска
|
||||
df -h
|
||||
|
||||
# Проверка PostgreSQL
|
||||
sudo -u postgres psql -c "SELECT pg_stat_database.datname, pg_stat_database.numbackends FROM pg_stat_database;"
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
```bash
|
||||
# Backup PostgreSQL
|
||||
sudo -u postgres pg_dump activitywatch > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Backup ActivityWatch data
|
||||
sudo tar -czf aw-backup_$(date +%Y%m%d).tar.gz /opt/activitywatch/data
|
||||
|
||||
# Restore
|
||||
sudo -u postgres psql activitywatch < backup_20240101.sql
|
||||
```
|
||||
|
||||
## Обновление
|
||||
|
||||
```bash
|
||||
# Остановите сервер
|
||||
sudo systemctl stop aw-server
|
||||
|
||||
# Backup
|
||||
sudo cp -r /opt/activitywatch /opt/activitywatch.backup
|
||||
|
||||
# Обновите binaries
|
||||
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.14.0/activitywatch-v0.14.0-linux-x86_64.zip
|
||||
unzip activitywatch-v0.14.0-linux-x86_64.zip -d /opt/activitywatch/
|
||||
|
||||
# Запустите сервер
|
||||
sudo systemctl start aw-server
|
||||
```
|
||||
|
||||
## Устранение проблем
|
||||
|
||||
### Server не запускается
|
||||
```bash
|
||||
# Проверьте логи
|
||||
sudo journalctl -u aw-server -n 50
|
||||
|
||||
# Проверьте порт
|
||||
sudo netstat -tlnp | grep 5600
|
||||
|
||||
# Проверьте права
|
||||
sudo ls -la /opt/activitywatch
|
||||
```
|
||||
|
||||
### PostgreSQL connection refused
|
||||
```bash
|
||||
# Проверьте статус
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Проверьте конфиг
|
||||
sudo cat /etc/postgresql/12/main/postgresql.conf | grep listen_addresses
|
||||
|
||||
# Проверьте firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Мониторинг стек](Monitoring-Setup)
|
||||
- [DLP Агрегация](DLP-Aggregation)
|
||||
@@ -0,0 +1,131 @@
|
||||
# WebUI Русификация
|
||||
|
||||
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
|
||||
|
||||
## Обзор
|
||||
|
||||
WebUI патчи добавляют:
|
||||
- Полный перевод интерфейса на русский
|
||||
- DLP центр для просмотра инцидентов
|
||||
- Центр управления правилами
|
||||
- Группы хостов
|
||||
- Русские стили и шрифты
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
Original WebUI → Patch Loader → DOM Injection → Style Injection → Translation → RU WebUI
|
||||
```
|
||||
|
||||
## Компоненты патчей
|
||||
|
||||
### Navigation Patches
|
||||
- Скрытие лишних пунктов меню
|
||||
- Добавление DLP навигации
|
||||
- Обновление ссылок
|
||||
|
||||
### DLP Center Injection
|
||||
- Центр просмотра инцидентов
|
||||
- Менеджер правил
|
||||
- Центр алертов
|
||||
- Архивирование
|
||||
|
||||
### Host Groups Center
|
||||
- Управление группами хостов
|
||||
- Визуализация по группам
|
||||
- Агрегированная статистика
|
||||
|
||||
### Translation
|
||||
- Перевод всех текстовых элементов
|
||||
- Русские форматы дат/чисел
|
||||
- Кириллические шрифты
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
# Применение патчей
|
||||
node aw-server/aw-ru-patch.js
|
||||
|
||||
# Очистка старых патчей
|
||||
node aw-server/aw-sw-cleanup.js
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### Host Groups Config
|
||||
```json
|
||||
{
|
||||
"host_groups": [
|
||||
{
|
||||
"id": "workstations",
|
||||
"name": "Рабочие станции",
|
||||
"hosts": ["WORKSTATION01", "WORKSTATION02"],
|
||||
"color": "#4ecdc4"
|
||||
},
|
||||
{
|
||||
"id": "servers",
|
||||
"name": "Серверы",
|
||||
"hosts": ["SERVER01", "SERVER02"],
|
||||
"color": "#ff6b6b"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### DLP Settings
|
||||
```json
|
||||
{
|
||||
"dlp_enabled": true,
|
||||
"review_center_enabled": true,
|
||||
"alerts_center_enabled": true,
|
||||
"auto_refresh_interval": 30,
|
||||
"default_severity_filter": "all"
|
||||
}
|
||||
```
|
||||
|
||||
## Функции WebUI
|
||||
|
||||
### DLP Review Center
|
||||
- Список инцидентов
|
||||
- Фильтрация по типам
|
||||
- Детальный просмотр
|
||||
- Архивирование
|
||||
- Экспорт
|
||||
|
||||
### DLP Rules Manager
|
||||
- Список правил
|
||||
- Создание/редактирование
|
||||
- Включение/выключение
|
||||
- Тестирование правил
|
||||
|
||||
### Host Groups Center
|
||||
- Управление группами
|
||||
- Визуализация
|
||||
- Статистика по группам
|
||||
|
||||
## Совместимость
|
||||
|
||||
- ActivityWatch v0.12.x - v0.14.x
|
||||
- Chrome 90+, Firefox 88+, Edge 90+, Safari 14+
|
||||
|
||||
## Производительность
|
||||
|
||||
- Lazy loading патчей
|
||||
- Кэширование переводов
|
||||
- Debouncing обновлений DOM
|
||||
- Virtual scrolling
|
||||
|
||||
## Отладка
|
||||
|
||||
```javascript
|
||||
// Режим разработки
|
||||
const DEBUG_MODE = true;
|
||||
|
||||
// Логирование патчей
|
||||
console.log('[AW-RU-Patch] Applying patch:', patchName);
|
||||
```
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/webui-patches.md)
|
||||
- [Группы хостов](Host-Groups)
|
||||
@@ -0,0 +1,172 @@
|
||||
# Установка на Windows
|
||||
|
||||
Установка ActivityWatch коллекторов на Windows рабочие станции.
|
||||
|
||||
## Требования
|
||||
|
||||
- Windows 10/11
|
||||
- PowerShell 5.1+
|
||||
- Административские права
|
||||
- Доступ к ActivityWatch Server
|
||||
|
||||
## Автоматическая установка
|
||||
|
||||
### Через Domain Controller (GPO)
|
||||
|
||||
```powershell
|
||||
# На Domain Controller
|
||||
.\windows\deploy-domain-users.ps1
|
||||
```
|
||||
|
||||
Этот скрипт:
|
||||
1. Создает GPO для развертывания
|
||||
2. Копирует коллекторы на рабочие станции
|
||||
3. Настраивает scheduled tasks
|
||||
4. Конфигурирует DLP политики
|
||||
|
||||
### Для одиночного пользователя
|
||||
|
||||
```powershell
|
||||
# Локально на workstation
|
||||
.\windows\deploy-single-user.ps1
|
||||
```
|
||||
|
||||
## Ручная установка
|
||||
|
||||
### 1. Установка ActivityWatch
|
||||
|
||||
```powershell
|
||||
# Скачайте installer с сайта ActivityWatch
|
||||
# Запустите installer
|
||||
|
||||
# Или через InnoSetup installer
|
||||
.\install-kit-awindows-*.exe
|
||||
```
|
||||
|
||||
### 2. Копирование коллекторов
|
||||
|
||||
```powershell
|
||||
# Создайте директорию
|
||||
New-Item -ItemType Directory -Path "C:\ProgramData\AWatch-rus" -Force
|
||||
|
||||
# Скопируйте коллекторы
|
||||
Copy-Item windows\dlp-endpoint-signals-collector.ps1 "C:\ProgramData\AWatch-rus\"
|
||||
Copy-Item windows\browser-domains-native-collector.ps1 "C:\ProgramData\AWatch-rus\"
|
||||
Copy-Item windows\email-outbound-collector.ps1 "C:\ProgramData\AWatch-rus\"
|
||||
```
|
||||
|
||||
### 3. Настройка конфигурации
|
||||
|
||||
```powershell
|
||||
# Создайте конфигурационный файл
|
||||
@{
|
||||
aw_server_url = "http://aw-server:5600"
|
||||
bucket_prefix = "aw-watcher-dlp-endpoint"
|
||||
heartbeat_interval = 60
|
||||
} | ConvertTo-Json | Out-File "C:\ProgramData\AWatch-rus\deployment-config.json"
|
||||
```
|
||||
|
||||
### 4. Настройка Scheduled Task
|
||||
|
||||
```powershell
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)
|
||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\ProgramData\AWatch-rus\dlp-endpoint-signals-collector.ps1"
|
||||
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action -RunLevel Highest
|
||||
```
|
||||
|
||||
## Конфигурация DLP политик
|
||||
|
||||
### Создание файла политик
|
||||
|
||||
```powershell
|
||||
@{
|
||||
clipboard_rules = @(
|
||||
@{
|
||||
pattern = "\b\d{4}-\d{4}-\d{4}-\d{4}\b"
|
||||
description = "Credit card numbers"
|
||||
severity = "high"
|
||||
action = "block"
|
||||
}
|
||||
)
|
||||
print_rules = @(
|
||||
@{
|
||||
printer_match = "*"
|
||||
document_keywords = @("confidential", "secret")
|
||||
action = "block"
|
||||
}
|
||||
)
|
||||
} | ConvertTo-Json -Depth 10 | Out-File "C:\ProgramData\AWatch-rus\dlp-policy.json"
|
||||
```
|
||||
|
||||
## Проверка установки
|
||||
|
||||
```powershell
|
||||
# Проверка статуса службы
|
||||
Get-ScheduledTask | Where-Object {$_.TaskName -like "*DLP*"}
|
||||
|
||||
# Проверка логов
|
||||
Get-Content "C:\ProgramData\AWatch-rus\logs\*.log" -Tail 50
|
||||
|
||||
# Проверка соединения с сервером
|
||||
Test-NetConnection -ComputerName aw-server -Port 5600
|
||||
```
|
||||
|
||||
## Устранение проблем
|
||||
|
||||
### Коллектор не запускается
|
||||
```powershell
|
||||
# Проверьте execution policy
|
||||
Get-ExecutionPolicy
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
# Проверьте права
|
||||
# Запустите PowerShell от администратора
|
||||
```
|
||||
|
||||
### Нет соединения с сервером
|
||||
```powershell
|
||||
# Проверьте firewall
|
||||
New-NetFirewallRule -DisplayName "Allow AW Server" -Direction Outbound -Protocol TCP -RemotePort 5600 -Action Allow
|
||||
|
||||
# Проверьте URL в конфиге
|
||||
Get-Content "C:\ProgramData\AWatch-rus\deployment-config.json"
|
||||
```
|
||||
|
||||
### DLP не работает
|
||||
```powershell
|
||||
# Проверьте файл политик
|
||||
Test-Path "C:\ProgramData\AWatch-rus\dlp-policy.json"
|
||||
|
||||
# Проверьте формат JSON
|
||||
Get-Content "C:\ProgramData\AWatch-rus\dlp-policy.json" | ConvertFrom-Json
|
||||
```
|
||||
|
||||
## Обновление
|
||||
|
||||
```powershell
|
||||
# Остановите задачи
|
||||
Unregister-ScheduledTask -TaskName "DLP Endpoint Collector" -Confirm:$false
|
||||
|
||||
# Обновите файлы
|
||||
Copy-Item windows\dlp-endpoint-signals-collector.ps1 "C:\ProgramData\AWatch-rus\" -Force
|
||||
|
||||
# Перезапустите задачи
|
||||
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
|
||||
```
|
||||
|
||||
## Удаление
|
||||
|
||||
```powershell
|
||||
# Остановите задачи
|
||||
Get-ScheduledTask | Where-Object {$_.TaskName -like "*DLP*"} | Unregister-ScheduledTask -Confirm:$false
|
||||
|
||||
# Удалите файлы
|
||||
Remove-Item "C:\ProgramData\AWatch-rus" -Recurse -Force
|
||||
|
||||
# Удалите ActivityWatch (через Programs and Features)
|
||||
```
|
||||
|
||||
## Подробнее
|
||||
|
||||
- [DLP Правила](DLP-Rules)
|
||||
- [Компоненты](Components)
|
||||
@@ -9,6 +9,10 @@
|
||||
- `windows/validate-deployment.ps1` — машинная проверка состояния и JSON-отчёт.
|
||||
- `windows/browser-domains-native-collector.ps1` — native collector доменов браузера с категоризацией.
|
||||
- `windows/dlp-endpoint-signals-collector.ps1` — Windows/RDP collector (clipboard/USB/print signals).
|
||||
- `windows/file-operations-collector.ps1` — collector файловых операций (create/delete/rename/archive hints).
|
||||
- `windows/worktime-session-collector.ps1` — collector RDP-сессий и активности.
|
||||
- `windows/install-standalone-service.ps1` — standalone установка агента как Windows Service (без Task Scheduler).
|
||||
- `windows/aw-standalone-service.ps1` — service wrapper для поддержания collector-процессов.
|
||||
- `windows/web-category-rules.example.json` — пример кастомных правил категоризации.
|
||||
- `windows/dlp-policy.example.json` — пример DLP-политики (phase-1: alerting incidents).
|
||||
|
||||
@@ -27,6 +31,14 @@
|
||||
- Корректно регистрирует задачи через `-LogonType Interactive` (совместимо с Windows Server, где `InteractiveToken` не поддерживается).
|
||||
- Поддерживает отключение шумных watcher'ов через `-AfkEnabled:$false` и `-WindowEnabled:$false`.
|
||||
|
||||
### Standalone InnoSetup (без Ansible, без Task Scheduler)
|
||||
|
||||
- InnoSetup запускает `install-standalone-service.ps1`.
|
||||
- Мастер спрашивает только `ServerHost` и `ServerPort`.
|
||||
- Создаётся сервис `AWatchRusStandaloneAgent` (auto-start, restart-on-failure).
|
||||
- Сервис управляет collector-скриптами и держит по одной рабочей копии каждого коллектора.
|
||||
- `deployment-config.json` формируется в `C:\ProgramData\AWatch-rus\deployment-config.json`.
|
||||
|
||||
Важно:
|
||||
|
||||
- Скриншот делается только при DLP-инциденте (`Send-DlpIncidentHeartbeat`), не по таймеру и не на обычной активности.
|
||||
|
||||
@@ -88,6 +88,35 @@ Start-ScheduledTask -TaskName 'ActivityWatch Launch [CONTOSO_user01]'
|
||||
|
||||
## Диагностика
|
||||
|
||||
### Standalone service не работает
|
||||
|
||||
Проверить сервис:
|
||||
|
||||
```powershell
|
||||
Get-Service AWatchRusStandaloneAgent
|
||||
sc.exe query AWatchRusStandaloneAgent
|
||||
```
|
||||
|
||||
Перезапуск:
|
||||
|
||||
```powershell
|
||||
Restart-Service AWatchRusStandaloneAgent
|
||||
```
|
||||
|
||||
Лог service wrapper:
|
||||
|
||||
```powershell
|
||||
Get-Content C:\ProgramData\AWatch-rus\logs\standalone-agent-service.log -Tail 200
|
||||
```
|
||||
|
||||
Проверить дочерние collector-процессы:
|
||||
|
||||
```powershell
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine -like '*AWatch-rus*collector*.ps1*' } |
|
||||
Select-Object ProcessId, SessionId, CommandLine
|
||||
```
|
||||
|
||||
Проверить задачи:
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -4,4 +4,9 @@ GRAFANA_ADMIN_PASSWORD=change_me_now
|
||||
GRAFANA_PORT=3000
|
||||
PROMETHEUS_PORT=9090
|
||||
SQL_EXPORTER_PORT=9399
|
||||
AW_EXPORTER_PORT=9398
|
||||
AW_SERVER_HOST=10.10.10.13
|
||||
AW_SERVER_PORT=5600
|
||||
AW_SERVER_SCHEME=http
|
||||
AW_SCRAPE_INTERVAL_SECONDS=30
|
||||
ONEC_DSN=postgres://onec_reader:change_me@10.10.10.20:5432/onec_db?sslmode=disable
|
||||
|
||||
+29
-21
@@ -3,44 +3,46 @@
|
||||
Готовый каркас для непрерывного сбора KPI из 1С и анализа в Grafana:
|
||||
|
||||
- `sql-exporter` читает SQL-представления KPI из БД 1С;
|
||||
- `aw-exporter` собирает метрики ActivityWatch и отдает их Prometheus;
|
||||
- `prometheus` собирает метрики и применяет alert-rules;
|
||||
- `grafana` поднимает datasource и дашборд автоматически.
|
||||
|
||||
## Полные пути
|
||||
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/.env.example`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/docker-compose.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql-exporter/sql_exporter.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql-exporter/collectors/onec_accounting_kpi.collector.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/prometheus.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/alerts.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/recording_rules.yml`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/grafana/dashboards/1c-accounting-overview.json`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/grafana/dashboards/1c-accounting-sre.json`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/postgres_views_template.sql`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/mssql_views_template.sql`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/discover_postgres_1c.sh`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/validate_kpi_views.sh`
|
||||
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/check_pipeline.sh`
|
||||
- `./.env.example`
|
||||
- `./docker-compose.yml`
|
||||
- `./sql-exporter/sql_exporter.yml`
|
||||
- `./sql-exporter/collectors/onec_accounting_kpi.collector.yml`
|
||||
- `./prometheus/prometheus.yml`
|
||||
- `./prometheus/alerts.yml`
|
||||
- `./prometheus/recording_rules.yml`
|
||||
- `./grafana/dashboards/1c-accounting-overview.json`
|
||||
- `./grafana/dashboards/1c-accounting-sre.json`
|
||||
- `./sql/postgres_views_template.sql`
|
||||
- `./sql/mssql_views_template.sql`
|
||||
- `./tools/discover_postgres_1c.sh`
|
||||
- `./tools/validate_kpi_views.sh`
|
||||
- `./tools/check_pipeline.sh`
|
||||
|
||||
## Быстрый запуск
|
||||
|
||||
1. Подготовьте env:
|
||||
|
||||
```bash
|
||||
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c
|
||||
cd grafana-1c
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. В `.env` задайте:
|
||||
|
||||
- `GRAFANA_ADMIN_USER`, `GRAFANA_ADMIN_PASSWORD`;
|
||||
- `ONEC_DSN` (DSN read-only пользователя в БД 1С).
|
||||
- `ONEC_DSN` (DSN read-only пользователя в БД 1С);
|
||||
- при необходимости `AW_SERVER_HOST`, `AW_SERVER_PORT`, `AW_SERVER_SCHEME`, `AW_EXPORTER_PORT` и `AW_SCRAPE_INTERVAL_SECONDS` для ActivityWatch exporter.
|
||||
|
||||
3. В БД 1С создайте KPI-представления:
|
||||
|
||||
- для PostgreSQL возьмите `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/postgres_views_template.sql`;
|
||||
- для MS SQL возьмите `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/mssql_views_template.sql`.
|
||||
- для PostgreSQL возьмите `./sql/postgres_views_template.sql`;
|
||||
- для MS SQL возьмите `./sql/mssql_views_template.sql`.
|
||||
|
||||
4. Поднимите стек:
|
||||
|
||||
@@ -52,7 +54,9 @@ docker compose up -d
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:9399/metrics | head
|
||||
curl -fsS http://127.0.0.1:9398/metrics | head
|
||||
curl -fsS http://127.0.0.1:9090/-/healthy
|
||||
curl -fsS http://127.0.0.1:3000/api/health
|
||||
```
|
||||
|
||||
Откройте Grafana: `http://<host>:3000`.
|
||||
@@ -62,23 +66,25 @@ curl -fsS http://127.0.0.1:9090/-/healthy
|
||||
Профилирование структуры 1С (PostgreSQL):
|
||||
|
||||
```bash
|
||||
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/discover_postgres_1c.sh \
|
||||
sh ./tools/discover_postgres_1c.sh \
|
||||
"postgres://user:pass@db-host:5432/db?sslmode=disable"
|
||||
```
|
||||
|
||||
Проверка KPI views:
|
||||
|
||||
```bash
|
||||
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/validate_kpi_views.sh \
|
||||
sh ./tools/validate_kpi_views.sh \
|
||||
"postgres://user:pass@db-host:5432/db?sslmode=disable"
|
||||
```
|
||||
|
||||
Проверка end-to-end пайплайна:
|
||||
|
||||
```bash
|
||||
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/check_pipeline.sh
|
||||
sh ./tools/check_pipeline.sh
|
||||
```
|
||||
|
||||
Скрипт проверяет полный путь сбора данных: `sql-exporter` и `aw-exporter` отдают обязательные метрики, Prometheus успешно выполняет запросы по scrape-targets, а Grafana отвечает на health/API, видит datasource `prometheus` и provisioned dashboards. Если стек запущен не из каталога репозитория, передайте путь к каталогу `grafana-1c` первым аргументом.
|
||||
|
||||
## Что контролируется
|
||||
|
||||
- Непроведенные документы (`onec_unposted_documents_total`)
|
||||
@@ -86,6 +92,8 @@ sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/check_pipeline.
|
||||
- Просроченная дебиторка (`onec_overdue_receivables_total`)
|
||||
- Ошибки проведения за 24ч (`onec_posting_errors_total`)
|
||||
- Свежесть данных из 1С (`onec_data_freshness_seconds`)
|
||||
- Доступность ActivityWatch API (`aw_up`)
|
||||
- Количество bucket/events ActivityWatch (`aw_buckets_total`, `aw_bucket_events_count`)
|
||||
|
||||
## Принципы безопасности
|
||||
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
services:
|
||||
aw-exporter:
|
||||
build:
|
||||
context: ./sql-exporter
|
||||
dockerfile: Dockerfile
|
||||
container_name: awrus-aw-exporter
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- AW_SERVER_HOST=${AW_SERVER_HOST:-10.10.10.13}
|
||||
- AW_SERVER_PORT=${AW_SERVER_PORT:-5600}
|
||||
- AW_SERVER_SCHEME=${AW_SERVER_SCHEME:-http}
|
||||
- EXPORTER_PORT=9398
|
||||
- SCRAPE_INTERVAL_SECONDS=${AW_SCRAPE_INTERVAL_SECONDS:-30}
|
||||
volumes:
|
||||
- ./sql-exporter/collectors/aw_activitywatch.py:/app/aw_activitywatch.py:ro
|
||||
ports:
|
||||
- "${AW_EXPORTER_PORT:-9398}:9398"
|
||||
command:
|
||||
- "python3"
|
||||
- "/app/aw_activitywatch.py"
|
||||
|
||||
sql-exporter:
|
||||
image: ghcr.io/burningalchemist/sql_exporter:latest
|
||||
container_name: awrus-sql-exporter
|
||||
@@ -30,6 +50,7 @@ services:
|
||||
ports:
|
||||
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||
depends_on:
|
||||
- aw-exporter
|
||||
- sql-exporter
|
||||
|
||||
grafana:
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"title": "ActivityWatch Overview",
|
||||
"tags": [
|
||||
"activitywatch",
|
||||
"monitoring"
|
||||
],
|
||||
"timezone": "browser",
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Total Buckets",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_buckets_total",
|
||||
"refId": "A",
|
||||
"legendFormat": "Total Buckets",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Events per Bucket",
|
||||
"type": "table",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_bucket_events_count",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"refId": "B",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time": true,
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Collector Status",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_collector_status",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"refId": "C",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"colorMode": "value"
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Events Timeline",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(aw_events_total[5m])",
|
||||
"legendFormat": "{{bucket}} - {{event_type}}",
|
||||
"refId": "D",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Last Event Timestamp",
|
||||
"type": "gauge",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_events_last_timestamp",
|
||||
"legendFormat": "{{bucket}}",
|
||||
"refId": "E",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s",
|
||||
"custom": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "yellow",
|
||||
"value": 3600
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 86400
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"uid": "activitywatch-overview",
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "30s",
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 1C-Buhgalteria
|
||||
- name: awatch-rus
|
||||
orgId: 1
|
||||
folder: "1C"
|
||||
folder: "AWatch-rus"
|
||||
type: file
|
||||
disableDeletion: true
|
||||
editable: false
|
||||
|
||||
@@ -11,6 +11,11 @@ scrape_configs:
|
||||
static_configs:
|
||||
- targets: ["localhost:9090"]
|
||||
|
||||
- job_name: aw_activitywatch_exporter
|
||||
static_configs:
|
||||
- targets: ["aw-exporter:9398"]
|
||||
scrape_interval: 30s
|
||||
|
||||
- job_name: onec_sql_exporter
|
||||
static_configs:
|
||||
- targets: ["sql-exporter:9399"]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install prometheus_client requests
|
||||
|
||||
COPY collectors/aw_activitywatch.py /app/aw_activitywatch.py
|
||||
|
||||
CMD ["python3", "/app/aw_activitywatch.py"]
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ActivityWatch Prometheus Exporter
|
||||
Собирает метрики из ActivityWatch API и экспонирует их в формате Prometheus.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from prometheus_client import Counter, Gauge, Info, start_http_server
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration
|
||||
AW_SERVER_HOST = os.getenv("AW_SERVER_HOST", "10.10.10.13")
|
||||
AW_SERVER_PORT = int(os.getenv("AW_SERVER_PORT", "5600"))
|
||||
AW_SERVER_SCHEME = os.getenv("AW_SERVER_SCHEME", "http")
|
||||
AW_API_BASE = os.getenv(
|
||||
"AW_API_BASE",
|
||||
f"{AW_SERVER_SCHEME}://{AW_SERVER_HOST}:{AW_SERVER_PORT}/api/0",
|
||||
)
|
||||
EXPORTER_PORT = int(os.getenv("EXPORTER_PORT", "9398"))
|
||||
SCRAPE_INTERVAL_SECONDS = int(os.getenv("SCRAPE_INTERVAL_SECONDS", "30"))
|
||||
|
||||
# Metrics
|
||||
aw_up = Gauge("aw_up", "ActivityWatch API availability: 1 if the last scrape succeeded, 0 otherwise")
|
||||
aw_buckets_total = Gauge("aw_buckets_total", "Total number of ActivityWatch buckets")
|
||||
aw_events_total = Counter("aw_events_total", "Total number of ActivityWatch events observed", ["bucket", "event_type"])
|
||||
aw_events_last_timestamp = Gauge("aw_events_last_timestamp", "Timestamp of last event in bucket", ["bucket"])
|
||||
aw_bucket_events_count = Gauge("aw_bucket_events_count", "Number of events sampled from bucket", ["bucket"])
|
||||
aw_collector_status = Gauge(
|
||||
"aw_collector_status",
|
||||
"ActivityWatch bucket collector status: 1 if bucket was observed during the last scrape",
|
||||
["bucket", "client", "hostname", "type"],
|
||||
)
|
||||
aw_server_info = Info("aw_server", "ActivityWatch server information")
|
||||
|
||||
|
||||
class ActivityWatchExporter:
|
||||
def __init__(self, api_base):
|
||||
self.api_base = api_base.rstrip("/")
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({"Accept": "application/json"})
|
||||
self.bucket_event_counts = {}
|
||||
|
||||
def get_buckets(self):
|
||||
"""Get all buckets from ActivityWatch API."""
|
||||
response = self.session.get(f"{self.api_base}/buckets", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_bucket_events(self, bucket_id, limit=1000):
|
||||
"""Get events from a specific bucket."""
|
||||
response = self.session.get(
|
||||
f"{self.api_base}/buckets/{bucket_id}/events",
|
||||
params={"limit": limit},
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
@staticmethod
|
||||
def event_type(event):
|
||||
data = event.get("data") or {}
|
||||
return str(data.get("app") or data.get("title") or event.get("$schema") or "unknown")
|
||||
|
||||
@staticmethod
|
||||
def event_timestamp(event):
|
||||
timestamp = event.get("timestamp", 0)
|
||||
if isinstance(timestamp, str):
|
||||
return datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()
|
||||
return float(timestamp or 0)
|
||||
|
||||
def collect_metrics(self):
|
||||
"""Collect metrics from ActivityWatch."""
|
||||
try:
|
||||
buckets = self.get_buckets()
|
||||
aw_up.set(1)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to get buckets: %s", exc)
|
||||
aw_up.set(0)
|
||||
return
|
||||
|
||||
aw_buckets_total.set(len(buckets))
|
||||
aw_server_info.info(
|
||||
{
|
||||
"host": AW_SERVER_HOST,
|
||||
"port": str(AW_SERVER_PORT),
|
||||
"scheme": AW_SERVER_SCHEME,
|
||||
"api_base": self.api_base,
|
||||
}
|
||||
)
|
||||
|
||||
aw_collector_status.clear()
|
||||
for bucket_id, bucket_data in buckets.items():
|
||||
client = str(bucket_data.get("client", "unknown"))
|
||||
hostname = str(bucket_data.get("hostname", "unknown"))
|
||||
bucket_type = str(bucket_data.get("type", "unknown"))
|
||||
|
||||
try:
|
||||
events = self.get_bucket_events(bucket_id)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to get events for %s: %s", bucket_id, exc)
|
||||
events = []
|
||||
|
||||
event_count = len(events)
|
||||
aw_bucket_events_count.labels(bucket=bucket_id).set(event_count)
|
||||
aw_collector_status.labels(bucket=bucket_id, client=client, hostname=hostname, type=bucket_type).set(1)
|
||||
|
||||
previous_count = self.bucket_event_counts.get(bucket_id)
|
||||
if previous_count is not None and event_count > previous_count:
|
||||
for event in events[: event_count - previous_count]:
|
||||
aw_events_total.labels(bucket=bucket_id, event_type=self.event_type(event)).inc()
|
||||
self.bucket_event_counts[bucket_id] = event_count
|
||||
|
||||
if events:
|
||||
try:
|
||||
aw_events_last_timestamp.labels(bucket=bucket_id).set(self.event_timestamp(events[0]))
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to parse last event timestamp for %s: %s", bucket_id, exc)
|
||||
|
||||
|
||||
def main():
|
||||
exporter = ActivityWatchExporter(AW_API_BASE)
|
||||
exporter.collect_metrics()
|
||||
|
||||
start_http_server(EXPORTER_PORT)
|
||||
logger.info("ActivityWatch exporter started on port %s", EXPORTER_PORT)
|
||||
logger.info("Scraping ActivityWatch API at %s", AW_API_BASE)
|
||||
|
||||
while True:
|
||||
time.sleep(SCRAPE_INTERVAL_SECONDS)
|
||||
exporter.collect_metrics()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,20 +1,95 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
STACK_DIR="${1:-/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c}"
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
STACK_DIR="${1:-$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)}"
|
||||
ENV_FILE="$STACK_DIR/.env"
|
||||
|
||||
echo "[*] Checking endpoints"
|
||||
curl -fsS http://127.0.0.1:9399/metrics >/tmp/awrus-onec-metrics.out
|
||||
curl -fsS http://127.0.0.1:9090/-/healthy >/tmp/awrus-prom-healthy.out
|
||||
curl -fsS "http://127.0.0.1:9090/api/v1/query?query=up%7Bjob%3D%22onec_sql_exporter%22%7D" >/tmp/awrus-prom-up.json
|
||||
curl -fsS "http://127.0.0.1:9090/api/v1/query?query=onec_data_freshness_seconds" >/tmp/awrus-prom-freshness.json
|
||||
env_value() {
|
||||
key="$1"
|
||||
default="$2"
|
||||
current=$(eval "printf '%s' \"\${$key:-}\"")
|
||||
if [ -n "$current" ]; then
|
||||
printf '%s' "$current"
|
||||
return
|
||||
fi
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
value=$(sed -n "s/^$key=//p" "$ENV_FILE" | tail -n 1)
|
||||
if [ -n "$value" ]; then
|
||||
printf '%s' "$value"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
printf '%s' "$default"
|
||||
}
|
||||
|
||||
echo "[*] Checking container status"
|
||||
cd "$STACK_DIR"
|
||||
docker compose ps
|
||||
GRAFANA_PORT=$(env_value GRAFANA_PORT 3000)
|
||||
PROMETHEUS_PORT=$(env_value PROMETHEUS_PORT 9090)
|
||||
SQL_EXPORTER_PORT=$(env_value SQL_EXPORTER_PORT 9399)
|
||||
AW_EXPORTER_PORT=$(env_value AW_EXPORTER_PORT 9398)
|
||||
GRAFANA_ADMIN_USER=$(env_value GRAFANA_ADMIN_USER admin)
|
||||
GRAFANA_ADMIN_PASSWORD=$(env_value GRAFANA_ADMIN_PASSWORD change_me_now)
|
||||
|
||||
TMP_DIR="${TMPDIR:-/tmp}"
|
||||
METRICS_OUT="$TMP_DIR/awrus-onec-metrics.out"
|
||||
AW_METRICS_OUT="$TMP_DIR/awrus-aw-metrics.out"
|
||||
PROM_HEALTH_OUT="$TMP_DIR/awrus-prom-healthy.out"
|
||||
PROM_UP_OUT="$TMP_DIR/awrus-prom-up.json"
|
||||
PROM_FRESHNESS_OUT="$TMP_DIR/awrus-prom-freshness.json"
|
||||
GRAFANA_HEALTH_OUT="$TMP_DIR/awrus-grafana-health.json"
|
||||
GRAFANA_DS_OUT="$TMP_DIR/awrus-grafana-datasources.json"
|
||||
GRAFANA_DASH_OUT="$TMP_DIR/awrus-grafana-dashboards.json"
|
||||
|
||||
require_metric() {
|
||||
metric_name="$1"
|
||||
metrics_file="$2"
|
||||
if ! grep -q "^$metric_name" "$metrics_file"; then
|
||||
echo "[!] Required metric '$metric_name' was not found in $metrics_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_prometheus_success() {
|
||||
file="$1"
|
||||
if ! grep -q '"status":"success"' "$file"; then
|
||||
echo "[!] Prometheus query did not return status=success: $file" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[*] Checking exporter endpoints"
|
||||
curl -fsS "http://127.0.0.1:$SQL_EXPORTER_PORT/metrics" >"$METRICS_OUT"
|
||||
curl -fsS "http://127.0.0.1:$AW_EXPORTER_PORT/metrics" >"$AW_METRICS_OUT"
|
||||
require_metric "onec_data_freshness_seconds" "$METRICS_OUT"
|
||||
require_metric "aw_up" "$AW_METRICS_OUT"
|
||||
|
||||
echo "[*] Checking Prometheus health and scrape targets"
|
||||
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/-/healthy" >"$PROM_HEALTH_OUT"
|
||||
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/api/v1/query?query=up%7Bjob%3D~%22onec_sql_exporter%7Caw_activitywatch_exporter%22%7D" >"$PROM_UP_OUT"
|
||||
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/api/v1/query?query=onec_data_freshness_seconds" >"$PROM_FRESHNESS_OUT"
|
||||
require_prometheus_success "$PROM_UP_OUT"
|
||||
require_prometheus_success "$PROM_FRESHNESS_OUT"
|
||||
|
||||
echo "[*] Checking Grafana health, datasource and dashboards"
|
||||
curl -fsS "http://127.0.0.1:$GRAFANA_PORT/api/health" >"$GRAFANA_HEALTH_OUT"
|
||||
curl -fsS -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" "http://127.0.0.1:$GRAFANA_PORT/api/datasources/uid/prometheus" >"$GRAFANA_DS_OUT"
|
||||
curl -fsS -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" "http://127.0.0.1:$GRAFANA_PORT/api/search?type=dash-db&query=" >"$GRAFANA_DASH_OUT"
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
echo "[*] Checking container status"
|
||||
cd "$STACK_DIR"
|
||||
docker compose ps
|
||||
else
|
||||
echo "[*] docker command not found; skipping container status"
|
||||
fi
|
||||
|
||||
echo "[+] Pipeline health artifacts:"
|
||||
echo " /tmp/awrus-onec-metrics.out"
|
||||
echo " /tmp/awrus-prom-healthy.out"
|
||||
echo " /tmp/awrus-prom-up.json"
|
||||
echo " /tmp/awrus-prom-freshness.json"
|
||||
echo " $METRICS_OUT"
|
||||
echo " $AW_METRICS_OUT"
|
||||
echo " $PROM_HEALTH_OUT"
|
||||
echo " $PROM_UP_OUT"
|
||||
echo " $PROM_FRESHNESS_OUT"
|
||||
echo " $GRAFANA_HEALTH_OUT"
|
||||
echo " $GRAFANA_DS_OUT"
|
||||
echo " $GRAFANA_DASH_OUT"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/home/igor/.local/share/uv/tools/graphifyy/bin/python3
|
||||
@@ -0,0 +1,101 @@
|
||||
# ActivityWatch-Russian Knowledge Graph Report
|
||||
|
||||
## Overview
|
||||
- **Total Nodes**: 404
|
||||
- **Total Edges**: 933
|
||||
- **Communities**: 27
|
||||
- **Source**: AST extraction (code-only corpus)
|
||||
|
||||
## Communities by Size
|
||||
|
||||
### Community 1 (62 nodes)
|
||||
- install_kit_awindows_20260427_211240_windows_dlp_endpoint_signals_collector_ps1
|
||||
- dlp_endpoint_signals_collector_get_deploymentconfig
|
||||
- dlp_endpoint_signals_collector_write_endpointlog
|
||||
- dlp_endpoint_signals_collector_invoke_awjsonpost
|
||||
- dlp_endpoint_signals_collector_ensure_bucket
|
||||
- ... and 57 more
|
||||
|
||||
### Community 2 (56 nodes)
|
||||
- aw_server_aw_ru_patch_js
|
||||
- aw_ru_patch_injectstyles
|
||||
- aw_ru_patch_hidenoisenavigation
|
||||
- aw_ru_patch_getcurrenthostfromhash
|
||||
- aw_ru_patch_ispvelikehost
|
||||
- ... and 51 more
|
||||
|
||||
### Community 3 (54 nodes)
|
||||
- install_kit_awindows_20260427_211240_windows_browser_domains_native_collector_ps1
|
||||
- browser_domains_native_collector_get_deploymentconfig
|
||||
- browser_domains_native_collector_write_collectorlog
|
||||
- browser_domains_native_collector_write_dlpincidentlog
|
||||
- browser_domains_native_collector_test_domainmatch
|
||||
- ... and 49 more
|
||||
|
||||
### Community 0 (38 nodes)
|
||||
- scripts_aggregate_dlp_events_py
|
||||
- aggregate_dlp_events_bucket
|
||||
- aggregate_dlp_events_awevent
|
||||
- aggregate_dlp_events_psycopgconnection
|
||||
- protocol
|
||||
- ... and 33 more
|
||||
|
||||
### Community 5 (34 nodes)
|
||||
- aw_ru_patch_isdlpsignalbucketroute
|
||||
- aw_ru_patch_getdlphostfrombucketid
|
||||
- aw_ru_patch_builddlpkey
|
||||
- aw_ru_patch_loadbucketevents
|
||||
- aw_ru_patch_serializerulematch
|
||||
- ... and 29 more
|
||||
|
||||
### Community 4 (34 nodes)
|
||||
- install_kit_awindows_20260427_211240_windows_email_outbound_collector_ps1
|
||||
- email_outbound_collector_get_deploymentconfig
|
||||
- email_outbound_collector_write_collectorlog
|
||||
- email_outbound_collector_invoke_awjsonpost
|
||||
- email_outbound_collector_ensure_bucket
|
||||
- ... and 29 more
|
||||
|
||||
### Community 8 (28 nodes)
|
||||
- aw_ru_patch_replacetext
|
||||
- aw_ru_patch_walk
|
||||
- aw_ru_patch_translateattributes
|
||||
- aw_ru_patch_ishomeroute
|
||||
- aw_ru_patch_getdefaulthostgroupsconfig
|
||||
- ... and 23 more
|
||||
|
||||
### Community 9 (18 nodes)
|
||||
- aw_ru_patch_getdlpbucketidfromhash
|
||||
- aw_ru_patch_generatedlpid
|
||||
- aw_ru_patch_awapijson
|
||||
- aw_ru_patch_ensureawbucket
|
||||
- aw_ru_patch_saveawheartbeat
|
||||
- ... and 13 more
|
||||
|
||||
### Community 10 (14 nodes)
|
||||
- install_kit_awindows_20260427_211240_windows_migrate_awatch_rus_paths_ps1
|
||||
- migrate_awatch_rus_paths_copy_directorycontents
|
||||
- migrate_awatch_rus_paths_copy_ifexists
|
||||
- migrate_awatch_rus_paths_convert_pathvalue
|
||||
- migrate_awatch_rus_paths_stop_awatchtaskset
|
||||
- ... and 9 more
|
||||
|
||||
### Community 6 (12 nodes)
|
||||
- grafana_1c_sql_exporter_collectors_aw_activitywatch_py
|
||||
- aw_activitywatch_activitywatchexporter
|
||||
- aw_activitywatch_activitywatchexporter_init
|
||||
- aw_activitywatch_activitywatchexporter_get_buckets
|
||||
- aw_activitywatch_activitywatchexporter_get_bucket_events
|
||||
- ... and 7 more
|
||||
|
||||
## File Types
|
||||
The graph was built from code files including:
|
||||
- PowerShell scripts (.ps1)
|
||||
- Python scripts (.py)
|
||||
- JavaScript patches (.js)
|
||||
- Configuration files
|
||||
|
||||
## Notes
|
||||
- This is a structural (AST-based) graph showing code relationships
|
||||
- No semantic extraction was performed (no docs/papers in corpus)
|
||||
- Communities represent clusters of related functions and modules
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_aw_server_aw_sw_cleanup_js", "label": "aw-sw-cleanup.js", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/aw-server/aw-sw-cleanup.js", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_domain_users_ps1", "label": "deploy-domain-users.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_deploy_ensemble_ps1", "label": "deploy-ensemble.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-ensemble.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_single_user_ps1", "label": "deploy-single-user.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_validate_deployment_ps1", "label": "validate-deployment.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/validate-deployment.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_hardening_recovery_ps1", "label": "hardening-recovery.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/hardening-recovery.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_hardening_recovery_ps1", "label": "hardening-recovery.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_deploy_single_user_ps1", "label": "deploy-single-user.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-single-user.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_validate_deployment_ps1", "label": "validate-deployment.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_ensemble_ps1", "label": "deploy-ensemble.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_aw_server_aw_sw_cleanup_js", "label": "aw-sw-cleanup.js", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/aw-server/aw-sw-cleanup.js", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_deploy_domain_users_ps1", "label": "deploy-domain-users.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-domain-users.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user