# Grafana dashboard'ы AW-rus

Документ описывает воспроизводимый импорт Grafana dashboard'ов из репозитория через Ansible и HTTP API Grafana.

## Что лежит в git

Version-controlled dashboard JSON находятся в каталоге `grafana/`:

- `grafana/detmir-rdp-user-activity-dashboard.json`
- `grafana/detmir-dlp-security-dashboard.json`
- `grafana/detmir-dlp-management-dashboard.json`
- `grafana/dlp-dashboard.json`

Их импортирует playbook:

- `ansible/deploy_grafana_dashboards.yml`

## Что импортируется

1. `AWatch-rus: Работа пользователей в RDP`
2. `AWatch-rus: DLP и ИБ обзор`
3. `AWatch-rus: ИБ сводка для руководства`
4. `AW-rus: DLP обзор`

По умолчанию playbook складывает их в folder `AWatch-rus` с `uid=awatch-rus`.

## Worktime panels и canonical users

Worktime dashboard'ы читают InfluxDB measurement
`aw_rdp_worktime_daily`/`aw_rdp_worktime_hourly` и группируют данные по user
label. Старые exporter versions писали raw `username`/`userId`, поэтому в
Influx могли остаться отдельные series для `USER5/user5`,
`Администратор/администратор`, machine account `SHARKON2025$` и битых строк с
Unicode replacement char `�`.

Version-controlled dashboard JSON должны сохранять защиту от старых series:

- `grafana/detmir-rdp-user-activity-dashboard.json`;
- `grafana/detmir-aw-main-dashboard.json`.

Для affected Flux queries обязательны правила:

- фильтровать `user_id !~ /\$$/` и `user_id !~ /�/`;
- мапить текущие DetMir accounts в canonical labels:
  `user1`, `user4`, `user5`, `Администратор`;
- grouping делать по `report_date,user` или `_time,user`;
- использовать `max(column: "_value")` после grouping, чтобы схлопнуть
  duplicate series без удвоения часов.

После импорта проверять панель `Вчера: активность по сотрудникам`. Ожидаемые
labels: `user1`, `user4`, `user5`, `Администратор`. Bad labels list должен быть
пустым для `USER*`, `SHARKON2025$`, `администратор`, `�` и labels, начинающихся
с `\`.

Owner-facing aggregate panel должен называться явно:

- title: `Все сотрудники: активное время по дням`;
- legend: `Все сотрудники`;
- field label: `Все сотрудники, ч`.

Не используйте `Команда` для этой панели: для владельца это выглядит как имя
отдельного пользователя или непонятной группы.

## Доступ владельца из портала

На production-контуре DetMir переход из `/portal` к Grafana dashboard'ам
выполняется без второго логина Grafana. Внешняя защита при этом остается на
gateway:

- `/portal/`, `/d/...`, `/dashboards` и `/r/grafana/` закрыты nginx Basic Auth;
- nginx после успешной gateway-авторизации передает в Grafana auth-proxy
  заголовки:
  - `X-WEBAUTH-USER: detmir-owner`;
  - `X-WEBAUTH-NAME: AWatch-rus Owner`;
  - `X-WEBAUTH-EMAIL: owner@awatch-rus.local`;
- Grafana принимает auth-proxy только от gateway `10.10.10.2`;
- созданный пользователь `detmir-owner` не является Grafana admin и получает
  viewer-доступ.

Основной dashboard для владельца:

```text
/d/detmir-rdp-user-activity/detmir3a-rabota-pol-zovatelej-v-rdp?orgId=1&from=now-7d&to=now&timezone=browser&var-host=SHARKON2025&refresh=5m
```

В портале он доступен как кнопка `Графики сотрудников`.

Не включайте `[auth.anonymous]` для решения этой задачи: это откроет Grafana на
внутреннем адресе `10.10.10.11:3000` без пользовательского контекста. Для
production используется только auth-proxy с whitelist gateway.

## Быстрый запуск

1. Подготовьте inventory и vars:

```bash
cd <PROJECT_ROOT>
cp ansible/inventory.example.ini ansible/inventory.ini
cp ansible/group_vars/grafana.example.yml ansible/group_vars/grafana.yml
```

2. Укажите `grafana_url` и заполните группу `[grafana]` в `ansible/inventory.ini`.

3. Перед запуском задайте пароль Grafana через переменную окружения:

```bash
export GRAFANA_ADMIN_PASSWORD='...'
```

4. Запустите импорт:

```bash
cd ansible
ansible-playbook -i inventory.ini deploy_grafana_dashboards.yml
```

## Что делает playbook

- проверяет `GET /api/health`;
- создает или обновляет folder `AWatch-rus`;
- импортирует dashboard JSON из репозитория;
- перезаписывает существующие dashboard'ы при `overwrite=true`;
- верифицирует каждый dashboard по `uid` через `GET /api/dashboards/uid/<uid>`.

## Production fallback при 403

Если Grafana API import запрещен (`403`) или provisioning не перезаписывает уже
существующую DB-запись dashboard, не правьте JSON только в UI. Сначала
обновите version-controlled dashboard JSON в git, затем примените один из
fallback paths.

Provisioning push:

```bash
scp grafana/detmir-aw-main-dashboard.json grafana/detmir-rdp-user-activity-dashboard.json igor@10.10.10.2:~/codex-dashboard-import/
ssh igor@10.10.10.2 'sudo pct push 201 /home/igor/codex-dashboard-import/detmir-aw-main-dashboard.json /etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json --perms 0644'
ssh igor@10.10.10.2 'sudo pct push 201 /home/igor/codex-dashboard-import/detmir-rdp-user-activity-dashboard.json /etc/grafana/provisioning/dashboards/aw/detmir-rdp-user-activity.json --perms 0644'
ssh igor@10.10.10.2 'sudo pct exec 201 -- bash -lc "cp -a /var/lib/grafana/grafana.db /var/lib/grafana/grafana.db.bak.$(date -u +%Y%m%dT%H%M%SZ); systemctl restart grafana-server"'
```

DB fallback: после backup `/var/lib/grafana/grafana.db` заменить только
`dashboard.data` rows по uid нужных dashboard'ов и перезапустить
`grafana-server`. Для исправления worktime-дублей production backup был:

```text
/var/lib/grafana/grafana.db.bak.20260609T013605Z
```

Для production rename `Команда` -> `Все сотрудники` на `2026-06-09` были
обновлены DB-записи:

- `detmir-rdp-user-activity`;
- `detmir-aw-main`.

Backup перед изменением:

```text
/var/lib/grafana/grafana.db.bak.20260609T020225Z
```

Контроль через gateway Grafana API:

- старый title count: `0`;
- новый title count: `1`;
- старая legend count: `0`;
- новая legend count: `1`;
- dashboard page: HTTP `200`, title `Grafana`.

Если после этого в уже открытой вкладке всё ещё видно `Команда`, сначала
сделайте hard refresh: это старое состояние браузера, а не старая DB-запись.

## Переменные

- `grafana_url` — base URL Grafana, например `http://10.20.30.11:3000`
- `grafana_admin_user` — Grafana admin/API user
- `grafana_admin_password` — пароль, рекомендуется через `GRAFANA_ADMIN_PASSWORD`
- `grafana_validate_tls` — включать ли проверку TLS-сертификата
- `grafana_folder_uid` — UID целевого folder
- `grafana_folder_title` — отображаемое имя folder
- `grafana_dashboards_import_overwrite` — перезаписывать ли dashboard'ы при повторном импорте

## Рекомендуемый эксплуатационный режим

- редактировать dashboard JSON в `grafana/`, а не править production только руками в UI;
- после изменений прогонять `deploy_grafana_dashboards.yml`, чтобы Grafana вернулась к version-controlled состоянию;
- для презентации использовать [docs/PRESENTATION_RU.md](PRESENTATION_RU.md), где уже лежат скриншоты ключевых экранов.
