Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
5a4064dc0f | ||
|
|
c97ffe2cbd | ||
|
|
f45ef0038d | ||
|
|
8088b19dc7 | ||
|
|
046aa3ed1d | ||
|
|
b6f019982d | ||
|
|
fae2e2ca14 | ||
|
|
f436950bda | ||
|
|
d7fedde69d | ||
|
|
7f58a49c0a | ||
|
|
8f10bef904 | ||
|
|
aca42caf0a | ||
|
|
9bdf28ed2b |
@@ -1,11 +1,13 @@
|
||||
# Local secrets
|
||||
secrets/deploy.secrets.env
|
||||
secrets/runtime.env
|
||||
|
||||
# Runtime / reports
|
||||
*.log
|
||||
*.tmp
|
||||
*.bak
|
||||
windows/*.report.json
|
||||
.rollout-logs/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
+11236
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
@@ -13,12 +14,16 @@
|
||||
- `docs/linux-remote-worker.md` — полный Linux remote-worker stack: GUI, SSH/console и browser admin UI вроде Proxmox `:8006`.
|
||||
- `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).
|
||||
- `pfsense/` — внешний poller для pfSense API и systemd unit под Debian/Ubuntu utility VM.
|
||||
- `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation, Windows/RDP DLP telemetry (`aw-dlp-incidents_*`, `aw-dlp-endpoint-signals_*`) и session-level presence для удалённых Windows/RDP пользователей (`aw-worktime-sessions_*`).
|
||||
- `scripts/quality-gate.sh` — локальный preflight-пайплайн проверок.
|
||||
- `scripts/aggregate_dlp_events.py` — сбор `aw-file-operations_*` и `aw-dlp-incidents_*` в SQLite/PostgreSQL.
|
||||
- `scripts/install_aw_linux_client.sh` — установка Linux bundle + autostart для remote AW server.
|
||||
- `scripts/install_aw_console_ssh_logger.sh` — user-space установка console/ssh logger.
|
||||
- `scripts/install_aw_linux_web_category_logger.sh` — user-space классификация browser admin UI по title/class.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# shellcheck disable=SC1007
|
||||
set -eu
|
||||
|
||||
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# shellcheck disable=SC1007
|
||||
set -eu
|
||||
|
||||
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
|
||||
@@ -31,6 +31,15 @@ cd ansible
|
||||
ansible-playbook -i inventory.ini deploy_aw_server.yml
|
||||
```
|
||||
|
||||
## Секреты (пароли) безопасно
|
||||
|
||||
Рекомендуемый способ не хранить пароли в репозитории — перед запуском экспортировать их в переменные окружения:
|
||||
|
||||
- Linux `aw_server` (SSH пароль root): `AW_SSH_PASSWORD`
|
||||
- Windows `aw_windows` (WinRM пароль): `AW_WINRM_PASSWORD`
|
||||
|
||||
В `group_vars/aw_server.yml` и `group_vars/windows.yml` они читаются через `lookup('env', ...)`.
|
||||
|
||||
## Полный установочный playbook (всё за один запуск)
|
||||
|
||||
Если нужно прогнать полный цикл одной командой:
|
||||
@@ -149,3 +158,13 @@ Playbook:
|
||||
- Для полного сценария CT создаётся автоматически через `pct create`.
|
||||
- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector.
|
||||
- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`.
|
||||
|
||||
## Prod rollout одной командой
|
||||
|
||||
Для ручного запуска с dry-run и логированием используйте:
|
||||
|
||||
```bash
|
||||
bash scripts/prod_rollout.sh
|
||||
```
|
||||
|
||||
Скрипт попросит `AW_SSH_PASSWORD` и `AW_WINRM_PASSWORD` интерактивно (ввод скрыт) и сложит логи в `.rollout-logs/`.
|
||||
|
||||
+551
-154
@@ -54,6 +54,11 @@
|
||||
- "{{ aw_server_webui_dir }}"
|
||||
- "{{ aw_server_webui_dir }}/js"
|
||||
- "{{ aw_server_data_dir }}"
|
||||
- "{{ aw_server_db_path | dirname }}"
|
||||
- "{{ aw_server_data_dir }}/.config"
|
||||
- "{{ aw_server_data_dir }}/.config/activitywatch"
|
||||
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
|
||||
- "{{ aw_server_data_dir }}/backups"
|
||||
- "{{ aw_server_log_dir }}"
|
||||
- /etc/activitywatch
|
||||
- "{{ aw_bootstrap_dir }}"
|
||||
@@ -74,100 +79,147 @@
|
||||
- "{{ aw_server_webui_dir }}"
|
||||
- "{{ aw_server_webui_dir }}/js"
|
||||
- "{{ aw_server_data_dir }}"
|
||||
- "{{ aw_server_db_path | dirname }}"
|
||||
- "{{ aw_server_data_dir }}/.config"
|
||||
- "{{ aw_server_data_dir }}/.config/activitywatch"
|
||||
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
|
||||
- "{{ aw_server_data_dir }}/backups"
|
||||
- "{{ aw_server_log_dir }}"
|
||||
|
||||
- name: Скачать архив релиза ActivityWatch
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ aw_server_download_url }}"
|
||||
dest: "{{ aw_archive_path }}"
|
||||
mode: "0644"
|
||||
- name: (Check mode) Пропустить установку релиза ActivityWatch
|
||||
ansible.builtin.debug:
|
||||
msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped."
|
||||
when: ansible_check_mode
|
||||
|
||||
- name: Распаковать релиз ActivityWatch
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ aw_archive_path }}"
|
||||
dest: "{{ aw_release_dir }}"
|
||||
remote_src: true
|
||||
extra_opts: ["-o"]
|
||||
- name: Установить релиз ActivityWatch (download/unarchive/install)
|
||||
when: not ansible_check_mode
|
||||
block:
|
||||
- name: Скачать архив релиза ActivityWatch
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ aw_server_download_url }}"
|
||||
dest: "{{ aw_archive_path }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: Найти распакованный каталог ActivityWatch
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
file_type: directory
|
||||
patterns: "activitywatch*"
|
||||
register: aw_release_find
|
||||
- name: Распаковать релиз ActivityWatch
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ aw_archive_path }}"
|
||||
dest: "{{ aw_release_dir }}"
|
||||
remote_src: true
|
||||
extra_opts: ["-o"]
|
||||
|
||||
- name: Найти бинарный файл AW server
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
file_type: file
|
||||
patterns:
|
||||
- aw-server-rust
|
||||
- aw-server
|
||||
register: aw_server_binary_find
|
||||
- name: Найти распакованный каталог ActivityWatch
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
recurse: true
|
||||
file_type: directory
|
||||
patterns: "activitywatch*"
|
||||
register: aw_release_find
|
||||
|
||||
- name: Найти каталог WebUI
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
file_type: directory
|
||||
patterns:
|
||||
- aw-webui
|
||||
- webui
|
||||
register: aw_webui_dir_find
|
||||
- name: Найти бинарный файл AW server
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
recurse: true
|
||||
file_type: file
|
||||
patterns:
|
||||
- aw-server-rust
|
||||
- aw-server
|
||||
register: aw_server_binary_find
|
||||
|
||||
- name: Сохранить пути распакованного релиза
|
||||
ansible.builtin.set_fact:
|
||||
aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
|
||||
aw_server_binary_path: "{{ (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
|
||||
aw_webui_source_path: "{{ (aw_webui_dir_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
|
||||
- name: Найти index.html WebUI
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
recurse: true
|
||||
file_type: file
|
||||
patterns:
|
||||
- index.html
|
||||
register: aw_webui_index_find
|
||||
|
||||
- name: Проверить, что компоненты релиза найдены
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_release_extracted is defined
|
||||
- aw_release_extracted | length > 0
|
||||
- aw_server_binary_path is defined
|
||||
- aw_server_binary_path | length > 0
|
||||
- aw_webui_source_path is defined
|
||||
- aw_webui_source_path | length > 0
|
||||
fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
|
||||
- name: Сохранить пути распакованного релиза (binary + webui index)
|
||||
ansible.builtin.set_fact:
|
||||
aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
|
||||
aw_server_binary_path: >-
|
||||
{{
|
||||
(
|
||||
(
|
||||
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
|
||||
| select('match', '.*/aw-server-rust$') | list | first
|
||||
)
|
||||
| default(
|
||||
(
|
||||
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
|
||||
),
|
||||
true
|
||||
)
|
||||
) | default('')
|
||||
}}
|
||||
aw_webui_index_path: >-
|
||||
{{
|
||||
(
|
||||
(
|
||||
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
|
||||
| select('search', '/static/index\\.html$') | list | first
|
||||
)
|
||||
| default(
|
||||
(
|
||||
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
|
||||
),
|
||||
true
|
||||
)
|
||||
) | default('')
|
||||
}}
|
||||
|
||||
- name: Создать каталог установленного релиза
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_release_install_dir }}"
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
- name: Сохранить каталог WebUI (dirname index.html)
|
||||
ansible.builtin.set_fact:
|
||||
aw_webui_source_path: "{{ aw_webui_index_path | dirname }}"
|
||||
|
||||
- name: Установить бинарный файл AW server
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: "{{ aw_server_binary_path }}"
|
||||
dest: "{{ aw_release_install_dir }}/aw-server-rust"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
- name: Проверить, что компоненты релиза найдены
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_release_extracted is defined
|
||||
- aw_release_extracted | length > 0
|
||||
- aw_server_binary_path is defined
|
||||
- aw_server_binary_path | length > 0
|
||||
- aw_webui_source_path is defined
|
||||
- aw_webui_source_path | length > 0
|
||||
fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
|
||||
|
||||
- name: Создать ссылку на активный бинарный файл AW server
|
||||
ansible.builtin.file:
|
||||
src: "{{ aw_release_install_dir }}/aw-server-rust"
|
||||
dest: /opt/activitywatch/bin/aw-server-rust
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
state: link
|
||||
force: true
|
||||
- name: Создать каталог установленного релиза
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_release_install_dir }}"
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Синхронизировать WebUI в RU каталог
|
||||
ansible.builtin.command:
|
||||
cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
|
||||
- name: Установить бинарный файл AW server
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: "{{ aw_server_binary_path }}"
|
||||
dest: "{{ aw_release_install_dir }}/aw-server-rust"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Настроить владельца файлов /opt/activitywatch
|
||||
ansible.builtin.file:
|
||||
path: /opt/activitywatch
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
recurse: true
|
||||
- name: Создать ссылку на активный бинарный файл AW server
|
||||
ansible.builtin.file:
|
||||
src: "{{ aw_release_install_dir }}/aw-server-rust"
|
||||
dest: /opt/activitywatch/bin/aw-server-rust
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Синхронизировать WebUI в RU каталог
|
||||
ansible.builtin.command:
|
||||
cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
|
||||
|
||||
- name: Настроить владельца файлов /opt/activitywatch
|
||||
ansible.builtin.file:
|
||||
path: /opt/activitywatch
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
recurse: true
|
||||
|
||||
- name: Установить systemd service из шаблона репозитория
|
||||
ansible.builtin.copy:
|
||||
@@ -184,78 +236,401 @@
|
||||
- Перезагрузить systemd
|
||||
- Перезапустить activitywatch
|
||||
|
||||
- name: Скопировать RU patch файлы WebUI из репозитория
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "{{ item.mode }}"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
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-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
|
||||
- name: (Check mode) Пропустить WebUI patch и запуск сервиса
|
||||
ansible.builtin.debug:
|
||||
msg: "ansible_check_mode=true: WebUI patch + service start + API checks are skipped."
|
||||
when: ansible_check_mode
|
||||
|
||||
- name: Проверить наличие index.html после копирования
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
register: aw_webui_ru_index
|
||||
- name: Применить WebUI RU patch и запустить сервис
|
||||
when: not ansible_check_mode
|
||||
block:
|
||||
- name: Скопировать RU patch файлы WebUI из репозитория
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "{{ item.mode }}"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
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: Проверить, что index.html доступен для RU patch
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_webui_ru_index.stat.exists
|
||||
fail_msg: "Не найден index.html WebUI для применения RU patch."
|
||||
- name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
|
||||
ansible.builtin.file:
|
||||
path: /root/bootstrap
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Удалить старые теги RU patch из index.html
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
regexp: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
|
||||
replace: ''
|
||||
- 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: Добавить cleanup script RU patch в index.html
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
regexp: '</head>'
|
||||
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
|
||||
- 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: Добавить загрузчик RU patch перед закрытием body
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
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 }}
|
||||
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: Записать /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_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 }}
|
||||
- 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: Включить и запустить сервис
|
||||
ansible.builtin.systemd:
|
||||
name: activitywatch-server.service
|
||||
enabled: true
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
- 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: Дождаться ответа API
|
||||
- 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"
|
||||
register: aw_webui_ru_index
|
||||
|
||||
- name: Проверить, что index.html доступен для RU patch
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_webui_ru_index.stat.exists
|
||||
fail_msg: "Не найден index.html WebUI для применения RU patch."
|
||||
|
||||
- name: Удалить старые теги RU patch из index.html
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
regexp: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
|
||||
replace: ''
|
||||
|
||||
- name: Добавить cleanup script RU patch в index.html
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
regexp: '</head>'
|
||||
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
|
||||
|
||||
- name: Добавить загрузчик RU patch перед закрытием body
|
||||
ansible.builtin.replace:
|
||||
path: "{{ aw_server_webui_dir }}/index.html"
|
||||
regexp: '</body>'
|
||||
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
|
||||
|
||||
- name: Скопировать merge script AW DB на сервер
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/scripts/merge_aw_server_dbs.py"
|
||||
dest: /usr/local/bin/merge_aw_server_dbs.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Проверить наличие legacy root DB
|
||||
ansible.builtin.stat:
|
||||
path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
|
||||
register: aw_legacy_root_db
|
||||
|
||||
- name: Проверить наличие target DB
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aw_server_db_path }}"
|
||||
register: aw_target_db
|
||||
|
||||
- name: Остановить сервис перед merge server DB
|
||||
ansible.builtin.systemd:
|
||||
name: activitywatch-server.service
|
||||
state: stopped
|
||||
when: aw_legacy_root_db.stat.exists | default(false)
|
||||
|
||||
- name: Создать backup каталоги server DB
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_server_data_dir }}/backups/db"
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
when: aw_legacy_root_db.stat.exists | default(false)
|
||||
|
||||
- name: Backup target DB перед merge
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: "{{ aw_server_db_path }}"
|
||||
dest: "{{ aw_server_data_dir }}/backups/db/target-before-merge-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
when:
|
||||
- aw_legacy_root_db.stat.exists | default(false)
|
||||
- aw_target_db.stat.exists | default(false)
|
||||
|
||||
- name: Backup legacy root DB перед merge
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
|
||||
dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
when: aw_legacy_root_db.stat.exists | default(false)
|
||||
|
||||
- name: Merge legacy root DB в target DB
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- python3
|
||||
- /usr/local/bin/merge_aw_server_dbs.py
|
||||
- --base
|
||||
- /root/.local/share/activitywatch/aw-server-rust/sqlite.db
|
||||
- --overlay
|
||||
- "{{ aw_server_db_path }}"
|
||||
- --output
|
||||
- "{{ aw_server_db_path }}.merged"
|
||||
when:
|
||||
- aw_legacy_root_db.stat.exists | default(false)
|
||||
- aw_target_db.stat.exists | default(false)
|
||||
|
||||
- name: Install merged DB as active target DB
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: "{{ aw_server_db_path }}.merged"
|
||||
dest: "{{ aw_server_db_path }}"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
when:
|
||||
- aw_legacy_root_db.stat.exists | default(false)
|
||||
- aw_target_db.stat.exists | default(false)
|
||||
|
||||
- name: Скопировать legacy root DB в target DB если target ещё не существует
|
||||
ansible.builtin.copy:
|
||||
remote_src: true
|
||||
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
|
||||
dest: "{{ aw_server_db_path }}"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
when:
|
||||
- aw_legacy_root_db.stat.exists | default(false)
|
||||
- not (aw_target_db.stat.exists | default(false))
|
||||
|
||||
- name: Записать aw-server-rust config.toml с разрешёнными CORS origin
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust/config.toml"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
content: |
|
||||
cors = [
|
||||
{% for origin in aw_server_cors_origins | default([]) %}
|
||||
"{{ origin }}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
- name: Включить и запустить сервис
|
||||
ansible.builtin.systemd:
|
||||
name: activitywatch-server.service
|
||||
enabled: true
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
|
||||
- name: Дождаться ответа API
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: aw_api
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: aw_api.status == 200
|
||||
|
||||
- name: Считать текущие server-side settings
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info"
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: aw_api
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: aw_api.status == 200
|
||||
register: aw_settings_current
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Считать текущие server-side views
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: aw_views_current
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Считать текущие server-side classes
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: aw_classes_current
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- 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 }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0644"
|
||||
content: "{{ item.payload | to_nice_json }}"
|
||||
loop:
|
||||
- name: settings
|
||||
payload: "{{ aw_settings_current.json | default({}) }}"
|
||||
- name: views
|
||||
payload: "{{ aw_views_current.json | default(none) }}"
|
||||
- name: classes
|
||||
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:
|
||||
@@ -277,16 +652,12 @@
|
||||
|
||||
- name: Вычислить worktime durationDefault из aw_worktime_from/to
|
||||
ansible.builtin.set_fact:
|
||||
aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}"
|
||||
aw_worktime_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}"
|
||||
aw_worktime_to_h: "{{ (aw_worktime_to | default('17:00')).split(':')[0] | int }}"
|
||||
aw_worktime_to_m: "{{ (aw_worktime_to | default('17:00')).split(':')[1] | int }}"
|
||||
aw_worktime_duration_default_derived: >-
|
||||
{{
|
||||
(
|
||||
(
|
||||
((aw_worktime_to_h | int) * 60 + (aw_worktime_to_m | int)) -
|
||||
((aw_worktime_from_h | int) * 60 + (aw_worktime_from_m | int))
|
||||
(((aw_worktime_to | default('17:00')).split(':')[0] | int) * 60 + ((aw_worktime_to | default('17:00')).split(':')[1] | int)) -
|
||||
(((aw_worktime_from | default('08:00')).split(':')[0] | int) * 60 + ((aw_worktime_from | default('08:00')).split(':')[1] | int))
|
||||
) * 60
|
||||
)
|
||||
}}
|
||||
@@ -314,20 +685,46 @@
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
|
||||
method: POST
|
||||
body: "{{ aw_worktime_start_of_day }}"
|
||||
body_format: json
|
||||
status_code: 200
|
||||
body: "\"{{ aw_worktime_start_of_day }}\""
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
status_code: [200, 201]
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Применить базовый период worktime (durationDefault seconds)
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault"
|
||||
method: POST
|
||||
body: "{{ aw_worktime_duration_default_effective }}"
|
||||
body_format: json
|
||||
status_code: 200
|
||||
body: "{{ aw_worktime_duration_default_effective | string }}"
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
status_code: [200, 201]
|
||||
when: aw_apply_worktime_settings | default(false) | bool
|
||||
|
||||
- name: Применить always_active_pattern для fallback без AFK
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/always_active_pattern"
|
||||
method: POST
|
||||
body: "\"{{ aw_server_always_active_pattern }}\""
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
status_code: [200, 201]
|
||||
when:
|
||||
- aw_apply_worktime_settings | default(false) | bool
|
||||
- (aw_server_always_active_pattern | default('') | string | length) > 0
|
||||
|
||||
- name: Применить landingpage профиля
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/landingpage"
|
||||
method: POST
|
||||
body: "\"{{ aw_server_landingpage }}\""
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
status_code: [200, 201]
|
||||
when:
|
||||
- aw_apply_worktime_settings | default(false) | bool
|
||||
- (aw_server_landingpage | default('') | string | length) > 0
|
||||
|
||||
handlers:
|
||||
- name: Перезагрузить systemd
|
||||
ansible.builtin.systemd:
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
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
|
||||
@@ -77,6 +78,8 @@
|
||||
- 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
|
||||
- deploy-domain-users.ps1
|
||||
@@ -86,6 +89,18 @@
|
||||
- web-category-rules.example.json
|
||||
- dlp-policy.example.json
|
||||
|
||||
- name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell)
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$toolkitDir = "{{ aw_windows_deploy_root }}\windows"
|
||||
$encIn = New-Object System.Text.UTF8Encoding($false)
|
||||
$encOut = New-Object System.Text.UTF8Encoding($true)
|
||||
Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object {
|
||||
$text = [System.IO.File]::ReadAllText($_.FullName, $encIn)
|
||||
[System.IO.File]::WriteAllText($_.FullName, $text, $encOut)
|
||||
}
|
||||
|
||||
- name: Загрузить список пользователей для доменного развёртывания
|
||||
ansible.windows.win_copy:
|
||||
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
|
||||
@@ -130,6 +145,7 @@
|
||||
StateRoot = "{{ aw_windows_state_root }}"
|
||||
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
|
||||
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
|
||||
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
|
||||
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
|
||||
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
|
||||
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
|
||||
@@ -149,6 +165,19 @@
|
||||
{% endif %}
|
||||
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
|
||||
|
||||
- name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
|
||||
$desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName })
|
||||
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) {
|
||||
if ($desired -notcontains [string]$task.TaskName) {
|
||||
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
& cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
- name: Принудительно запустить ActivityWatch recovery и launch tasks
|
||||
when: aw_windows_force_task_restart | bool
|
||||
ansible.windows.win_powershell:
|
||||
@@ -171,6 +200,7 @@
|
||||
when:
|
||||
- aw_windows_api_smoke_check_enabled | bool
|
||||
- aw_windows_afk_enabled | bool
|
||||
- aw_windows_hostname_result.stdout is defined
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_api_smoke_check_bucket_effective: >-
|
||||
{{
|
||||
@@ -179,54 +209,51 @@
|
||||
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
|
||||
}}
|
||||
|
||||
- name: Дождаться свежих AFK событий на AW server
|
||||
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
|
||||
when:
|
||||
- aw_windows_api_smoke_check_enabled | bool
|
||||
- aw_windows_afk_enabled | bool
|
||||
delegate_to: localhost
|
||||
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
|
||||
return_content: true
|
||||
register: aw_windows_api_smoke
|
||||
until: >
|
||||
aw_windows_api_smoke.status == 200 and
|
||||
(aw_windows_api_smoke.json | length) > 0 and
|
||||
(
|
||||
aw_windows_api_smoke.json
|
||||
| selectattr('data.status', 'equalto', 'not-afk')
|
||||
| list
|
||||
| length
|
||||
) > 0
|
||||
retries: 10
|
||||
delay: 6
|
||||
status_code: 200
|
||||
register: aw_windows_api_smoke_result
|
||||
until: aw_windows_api_smoke_result.json | length > 0
|
||||
retries: 5
|
||||
delay: 5
|
||||
ignore_errors: true
|
||||
|
||||
- name: Выполнить валидацию и сохранить отчёт на целевом Windows host
|
||||
- name: Валидировать развёртывание на эндпоинте
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
||||
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
||||
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
|
||||
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
||||
if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) {
|
||||
throw "Проверка развёртывания ActivityWatch завершилась ошибкой. Отчёт: {{ aw_windows_validation_remote_path }}"
|
||||
}
|
||||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
||||
return $result
|
||||
|
||||
- name: Создать локальный каталог для validation reports
|
||||
- name: Создать локальную директорию для отчётов валидации
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_windows_validation_local_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Забрать validation report
|
||||
- name: Стянуть отчёт валидации с эндпоинта
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ aw_windows_validation_remote_path }}"
|
||||
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
flat: true
|
||||
|
||||
- name: Показать путь к отчёту
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Windows/RDP развёртывание завершено на {{ inventory_hostname }}."
|
||||
- "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
- name: Проверить статус валидации
|
||||
ansible.builtin.shell: |
|
||||
python3 - <<'PY'
|
||||
import json, sys
|
||||
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r') as f:
|
||||
data = json.load(f)
|
||||
if not data.get('overallOk', False):
|
||||
print(f"Validation failed for {{ inventory_hostname }}: {data.get('summary', 'Unknown error')}")
|
||||
sys.exit(1)
|
||||
PY
|
||||
delegate_to: localhost
|
||||
when: aw_windows_fail_on_validation_error | bool
|
||||
|
||||
@@ -4,15 +4,26 @@ 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_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 }}"
|
||||
|
||||
# Опционально: применить базовые категории и views для рабочего времени через AW settings API.
|
||||
# Внимание: это перезаписывает существующие server-side settings/classes/views.
|
||||
aw_apply_worktime_settings: false
|
||||
# Применить базовые категории и views для рабочего времени через AW settings API.
|
||||
# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes.
|
||||
aw_apply_worktime_settings: true
|
||||
|
||||
# Дополнительные origin для aw-server-rust CORS.
|
||||
# Обязательно включите тот origin, с которого реально открывается Web UI.
|
||||
aw_server_cors_origins:
|
||||
- "http://127.0.0.1:5600"
|
||||
- "http://localhost:5600"
|
||||
- "http://10.10.10.13:5600"
|
||||
- "http://aw-server:5600"
|
||||
|
||||
# Опциональные значения периода рабочего времени в Web UI.
|
||||
# startOfDay задаёт границу дня и стартовое время окна отчёта.
|
||||
@@ -22,3 +33,5 @@ aw_apply_worktime_settings: false
|
||||
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/"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
aw_server_version: "v0.13.2"
|
||||
aw_server_download_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-linux-x86_64.zip"
|
||||
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_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: "/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://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 }}"
|
||||
@@ -0,0 +1,9 @@
|
||||
# Secret handling:
|
||||
# - put the real SSH password into env var before running Ansible:
|
||||
# export AW_SSH_PASSWORD='...'
|
||||
ansible_password: "{{ lookup('env', 'AW_SSH_PASSWORD') }}"
|
||||
|
||||
ansible_become: true
|
||||
ansible_become_method: sudo
|
||||
# If sudo password differs, set AW_SUDO_PASSWORD. Otherwise it will reuse AW_SSH_PASSWORD.
|
||||
ansible_become_password: "{{ lookup('env', 'AW_SUDO_PASSWORD') | default(lookup('env', 'AW_SSH_PASSWORD'), true) }}"
|
||||
@@ -0,0 +1,54 @@
|
||||
# Secret handling:
|
||||
# - put the real password into env var before running Ansible:
|
||||
# export AW_WINRM_PASSWORD='...'
|
||||
ansible_password: "{{ lookup('env', 'AW_WINRM_PASSWORD') }}"
|
||||
|
||||
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
|
||||
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
|
||||
aw_windows_server_scheme: "http"
|
||||
aw_windows_server_host: "10.10.10.13"
|
||||
aw_windows_server_port: 5600
|
||||
|
||||
aw_windows_package_version: "v0.13.2"
|
||||
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
|
||||
aw_windows_package_zip_path: ""
|
||||
|
||||
aw_windows_domain: "SHARKON2025"
|
||||
aw_windows_users:
|
||||
- user1
|
||||
- user2
|
||||
- user3
|
||||
- user4
|
||||
- user5
|
||||
aw_windows_extra_users: []
|
||||
|
||||
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
|
||||
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
|
||||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||||
aw_windows_logon_marker_enabled: true
|
||||
aw_windows_skip_hardening: false
|
||||
|
||||
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
|
||||
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
|
||||
|
||||
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||||
aw_windows_fail_on_validation_error: 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"
|
||||
|
||||
aw_windows_api_smoke_check_enabled: true
|
||||
aw_windows_api_smoke_check_bucket: ""
|
||||
aw_windows_api_smoke_check_limit: 10
|
||||
@@ -23,6 +23,7 @@ aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
|
||||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||||
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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[aw_server]
|
||||
localhost ansible_connection=local ansible_user=root
|
||||
|
||||
[aw_windows]
|
||||
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
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
- name: Post-deploy validation for Windows/RDP AWatch-rus
|
||||
hosts: aw_windows
|
||||
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
|
||||
aw_windows_api_smoke_check_enabled: true
|
||||
aw_windows_api_smoke_check_bucket: ""
|
||||
aw_windows_api_smoke_check_limit: 10
|
||||
|
||||
tasks:
|
||||
- name: Принудительно запустить ActivityWatch recovery и launch tasks
|
||||
when: aw_windows_force_task_restart | bool
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
||||
Get-ScheduledTask |
|
||||
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
|
||||
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
|
||||
|
||||
- name: Получить Windows hostname для AW smoke-check bucket
|
||||
when: aw_windows_api_smoke_check_enabled | bool
|
||||
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
|
||||
register: aw_windows_hostname_result
|
||||
changed_when: false
|
||||
|
||||
- name: Вычислить AW AFK smoke-check bucket
|
||||
when: aw_windows_api_smoke_check_enabled | bool
|
||||
ansible.builtin.set_fact:
|
||||
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: Дождаться свежих AFK событий на AW server
|
||||
when: aw_windows_api_smoke_check_enabled | bool
|
||||
delegate_to: localhost
|
||||
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: >
|
||||
aw_windows_api_smoke.status == 200 and
|
||||
(aw_windows_api_smoke.json | length) > 0 and
|
||||
(
|
||||
aw_windows_api_smoke.json
|
||||
| selectattr('data.status', 'equalto', 'not-afk')
|
||||
| list
|
||||
| length
|
||||
) > 0
|
||||
retries: 10
|
||||
delay: 6
|
||||
|
||||
- name: Выполнить валидацию и сохранить отчёт на целевом Windows host
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
||||
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
|
||||
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
||||
if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) {
|
||||
throw "ActivityWatch validation failed. Report: {{ aw_windows_validation_remote_path }}"
|
||||
}
|
||||
|
||||
- name: Создать локальный каталог для validation reports
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_windows_validation_local_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Забрать validation report
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ aw_windows_validation_remote_path }}"
|
||||
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
flat: true
|
||||
|
||||
- name: Показать путь к отчёту
|
||||
ansible.builtin.debug:
|
||||
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
|
||||
|
||||
@@ -9,7 +9,7 @@ EnvironmentFile=/etc/activitywatch/aw-server.env
|
||||
User=__AW_SERVER_USER__
|
||||
Group=__AW_SERVER_GROUP__
|
||||
WorkingDirectory=__AW_SERVER_DATA_DIR__
|
||||
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"'
|
||||
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT" --dbpath "$AW_SERVER_DB_PATH" --webpath "$AW_SERVER_WEBUI_DIR"'
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StateDirectory=activitywatch
|
||||
@@ -17,7 +17,7 @@ LogsDirectory=activitywatch
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
ProtectHome=read-only
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -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 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 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,103 @@
|
||||
# Central DLP aggregator prototype
|
||||
|
||||
`scripts/aggregate_dlp_events.py` collects Phase 2 DLP telemetry from ActivityWatch buckets and stores normalized rows in one database for Grafana/SIEM-style reporting.
|
||||
|
||||
## Streams
|
||||
|
||||
The prototype reads:
|
||||
|
||||
- `aw-file-operations_*` (`aw.file.operation`) — file create/delete/rename telemetry, including `archiveHint`.
|
||||
- `aw-dlp-incidents_*` (`aw.dlp.incident`) — browser/endpoint DLP incidents and screenshot metadata when available.
|
||||
|
||||
## SQLite smoke test
|
||||
|
||||
SQLite is the default so the collector can be tested without deploying PostgreSQL:
|
||||
|
||||
```bash
|
||||
python3 scripts/aggregate_dlp_events.py \
|
||||
--aw-url http://10.10.10.13:5600/api/0 \
|
||||
--sqlite-path data/dlp-events.sqlite3 \
|
||||
--lookback-hours 24
|
||||
```
|
||||
|
||||
Useful checks:
|
||||
|
||||
```bash
|
||||
sqlite3 data/dlp-events.sqlite3 \
|
||||
"select stream_type, hostname, count(*) from dlp_events group by 1,2 order by 3 desc;"
|
||||
|
||||
sqlite3 data/dlp-events.sqlite3 \
|
||||
"select event_ts, hostname, username, file_path from dlp_file_operations where archive_hint = 1 order by event_ts desc limit 20;"
|
||||
```
|
||||
|
||||
## PostgreSQL mode
|
||||
|
||||
For centralized reporting, pass a DSN through an environment variable instead of committing secrets:
|
||||
|
||||
```bash
|
||||
export DLP_AGGREGATOR_POSTGRES_DSN='postgresql://aw_dlp:${PASSWORD}@postgres.internal:5432/aw_dlp'
|
||||
python3 -m pip install 'psycopg[binary]'
|
||||
python3 scripts/aggregate_dlp_events.py \
|
||||
--aw-url http://10.10.10.13:5600/api/0
|
||||
```
|
||||
|
||||
Minimum database bootstrap:
|
||||
|
||||
```sql
|
||||
create database aw_dlp;
|
||||
create user aw_dlp_ingest with password '<strong generated password>';
|
||||
grant connect on database aw_dlp to aw_dlp_ingest;
|
||||
grant usage, create on schema public to aw_dlp_ingest;
|
||||
```
|
||||
|
||||
The script creates:
|
||||
|
||||
- table `dlp_events`
|
||||
- view `dlp_file_operations`
|
||||
- view `dlp_incidents`
|
||||
|
||||
## Incremental state
|
||||
|
||||
By default, the aggregator stores the last successful end timestamp in:
|
||||
|
||||
```text
|
||||
data/dlp-aggregator-state.json
|
||||
```
|
||||
|
||||
Future runs resume from that timestamp with a small overlap window to avoid missing late events. Duplicate inserts are ignored by `(bucket_id, event_id)`.
|
||||
|
||||
## Scheduling example
|
||||
|
||||
Cron every minute:
|
||||
|
||||
```cron
|
||||
* * * * * cd /opt/AWatch-rus && /usr/bin/python3 scripts/aggregate_dlp_events.py --aw-url http://10.10.10.13:5600/api/0 >> /var/log/aw-dlp-aggregator.log 2>&1
|
||||
```
|
||||
|
||||
## Example Grafana queries
|
||||
|
||||
Archive creation by user:
|
||||
|
||||
```sql
|
||||
select
|
||||
date_trunc('minute', event_ts) as time,
|
||||
hostname,
|
||||
username,
|
||||
count(*) as archives
|
||||
from dlp_file_operations
|
||||
where archive_hint = true
|
||||
group by 1, 2, 3
|
||||
order by 1 desc;
|
||||
```
|
||||
|
||||
DLP incidents by severity:
|
||||
|
||||
```sql
|
||||
select
|
||||
date_trunc('hour', event_ts) as time,
|
||||
severity,
|
||||
count(*) as incidents
|
||||
from dlp_incidents
|
||||
group by 1, 2
|
||||
order by 1 desc;
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -29,8 +29,44 @@
|
||||
|
||||
- USB/print/clipboard collectors (endpoint signals) — внедрено.
|
||||
- Incident pipeline расширен на endpoint события — внедрено.
|
||||
- File-operation telemetry (create/copy/archive/upload hints) — в backlog.
|
||||
- Central incident aggregation/export — в backlog.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -1,4 +1,23 @@
|
||||
services:
|
||||
aw-exporter:
|
||||
build:
|
||||
context: ./sql-exporter
|
||||
dockerfile: Dockerfile
|
||||
container_name: awrus-aw-exporter
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- AW_SERVER_HOST=10.10.10.13
|
||||
- AW_SERVER_PORT=5600
|
||||
- AW_SERVER_SCHEME=http
|
||||
- EXPORTER_PORT=9398
|
||||
volumes:
|
||||
- ./sql-exporter/collectors/aw_activitywatch.py:/app/aw_activitywatch.py:ro
|
||||
ports:
|
||||
- "9398:9398"
|
||||
command:
|
||||
- "python3"
|
||||
- "/app/aw_activitywatch.py"
|
||||
|
||||
sql-exporter:
|
||||
image: ghcr.io/burningalchemist/sql_exporter:latest
|
||||
container_name: awrus-sql-exporter
|
||||
@@ -30,6 +49,7 @@ services:
|
||||
ports:
|
||||
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||
depends_on:
|
||||
- aw-exporter
|
||||
- sql-exporter
|
||||
|
||||
grafana:
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"dashboard": {
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Events per Bucket",
|
||||
"type": "table",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_bucket_events_count",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time": true,
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Collector Status",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_collector_status",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"colorMode": "value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Events Timeline",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(aw_events_total[5m])",
|
||||
"legendFormat": "{{bucket}} - {{event_type}}",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Last Event Timestamp",
|
||||
"type": "gauge",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "aw_events_last_timestamp",
|
||||
"legendFormat": "{{bucket}}",
|
||||
"refId": "E"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s",
|
||||
"custom": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{"color": "red", "value": 0},
|
||||
{"color": "yellow", "value": 3600},
|
||||
{"color": "green", "value": 86400}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -9,3 +9,12 @@ providers:
|
||||
editable: false
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
|
||||
- name: ActivityWatch
|
||||
orgId: 2
|
||||
folder: "ActivityWatch"
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: true
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: true
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ActivityWatch Prometheus Exporter
|
||||
Собирает метрики из ActivityWatch API и экспонирует их в формате Prometheus.
|
||||
"""
|
||||
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
from prometheus_client import start_http_server, Gauge, Counter, Histogram, Info
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration
|
||||
AW_SERVER_HOST = "10.10.10.13"
|
||||
AW_SERVER_PORT = 5600
|
||||
AW_SERVER_SCHEME = "http"
|
||||
AW_API_BASE = f"{AW_SERVER_SCHEME}://{AW_SERVER_HOST}:{AW_SERVER_PORT}/api/0"
|
||||
EXPORTER_PORT = 9398
|
||||
|
||||
# Metrics
|
||||
aw_buckets_total = Gauge('aw_buckets_total', 'Total number of ActivityWatch buckets')
|
||||
aw_events_total = Counter('aw_events_total', 'Total number of ActivityWatch events', ['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 in bucket', ['bucket'])
|
||||
aw_collector_status = Info('aw_collector_status', 'Status of ActivityWatch collectors')
|
||||
aw_server_info = Info('aw_server_info', 'ActivityWatch server information')
|
||||
|
||||
class ActivityWatchExporter:
|
||||
def __init__(self, api_base):
|
||||
self.api_base = api_base
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({'Accept': 'application/json'})
|
||||
self.bucket_cache = {}
|
||||
|
||||
def get_buckets(self):
|
||||
"""Get all buckets from ActivityWatch API."""
|
||||
try:
|
||||
response = self.session.get(f"{self.api_base}/buckets", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get buckets: {e}")
|
||||
return {}
|
||||
|
||||
def get_bucket_events(self, bucket_id, limit=1):
|
||||
"""Get events from a specific bucket."""
|
||||
try:
|
||||
response = self.session.get(
|
||||
f"{self.api_base}/buckets/{bucket_id}/events",
|
||||
params={'limit': limit},
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get events for {bucket_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_bucket_info(self, bucket_id):
|
||||
"""Get detailed info about a bucket."""
|
||||
try:
|
||||
response = self.session.get(f"{self.api_base}/buckets/{bucket_id}", timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get info for {bucket_id}: {e}")
|
||||
return {}
|
||||
|
||||
def collect_metrics(self):
|
||||
"""Collect metrics from ActivityWatch."""
|
||||
buckets = self.get_buckets()
|
||||
|
||||
# Update bucket count
|
||||
aw_buckets_total.set(len(buckets))
|
||||
|
||||
# Server info
|
||||
aw_server_info.info({
|
||||
'host': AW_SERVER_HOST,
|
||||
'port': AW_SERVER_PORT,
|
||||
'scheme': AW_SERVER_SCHEME,
|
||||
'api_base': self.api_base
|
||||
})
|
||||
|
||||
# Collector status
|
||||
collectors = {}
|
||||
for bucket_id, bucket_data in buckets.items():
|
||||
client = bucket_data.get('client', 'unknown')
|
||||
hostname = bucket_data.get('hostname', 'unknown')
|
||||
bucket_type = bucket_data.get('type', 'unknown')
|
||||
|
||||
# Count events
|
||||
events = self.get_bucket_events(bucket_id, limit=1000)
|
||||
event_count = len(events)
|
||||
aw_bucket_events_count.labels(bucket=bucket_id).set(event_count)
|
||||
|
||||
# Last event timestamp
|
||||
if events:
|
||||
last_event = events[0]
|
||||
timestamp = last_event.get('timestamp', 0)
|
||||
try:
|
||||
# Convert to Unix timestamp if needed
|
||||
if isinstance(timestamp, str):
|
||||
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
||||
unix_ts = dt.timestamp()
|
||||
else:
|
||||
unix_ts = timestamp
|
||||
aw_events_last_timestamp.labels(bucket=bucket_id).set(unix_ts)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Collector status
|
||||
collector_key = f"{hostname}_{client}"
|
||||
collectors[collector_key] = {
|
||||
'status': 'active',
|
||||
'bucket': bucket_id,
|
||||
'type': bucket_type,
|
||||
'events': event_count
|
||||
}
|
||||
|
||||
aw_collector_status.info(collectors)
|
||||
|
||||
def main():
|
||||
exporter = ActivityWatchExporter(AW_API_BASE)
|
||||
|
||||
# Initial collection
|
||||
exporter.collect_metrics()
|
||||
|
||||
# Start HTTP server
|
||||
start_http_server(EXPORTER_PORT)
|
||||
logger.info(f"ActivityWatch exporter started on port {EXPORTER_PORT}")
|
||||
logger.info(f"Scraping ActivityWatch API at {AW_API_BASE}")
|
||||
|
||||
# Collect metrics every 30 seconds
|
||||
while True:
|
||||
time.sleep(30)
|
||||
exporter.collect_metrics()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user