Динамический виджет-навигатор с RLS в AW BI (Часть II)

Динамический виджет-навигатор с RLS в AW BI

О чём эта статья. Практический кейс: настраиваем виджет с кнопками дашбордов, который у каждого пользователя выглядит по-своему — показывает только то, что ему доступно. Это вторая часть. В первой части мы разобрали, как устроена система прав доступа и RLS в AW BI.


Описание кейса

Делаем виджет-навигатор, который показывает каждому пользователю только те дашборды, к которым у него есть доступ. Кнопки формируются динамически из данных модели. Наш пример носит теоретический характер и число дашбордов, как и пользователей с разными уровнями доступа в примере не так много, но этот случай легко масштабируется на создание больших навигационных панелей для компании.

Группы пользователей и их доступ:

Группа (department) Кто это Видит дашборды
hr HR-отдел Показатели HR
sales Коммерческий отдел Продажи
warehouse Складской отдел Динамика поступления на склады
all Руководство Все три дашборда

Тестовые пользователи:

login department Что увидит в виджете
test_user hr Только кнопку «Показатели HR»
a.khoroshilova sales Только кнопку «Продажи»
s.studenikin all Все три кнопки

Шаг 1 — Подготовка данных

Нам нужны два Excel-файла, которые станут источниками данных для двух моделей.

Файл 1: user_permissions.xlsx

Таблица с логинами пользователей и их атрибутом department. По этим данным система будет понимать, кто из какого отдела.

Эти данные могут приходить из внешней базы данных, динамически. В рамках демонстрации мы упростили случай до получения данных из excel документа.

login department
test_user hr
a.khoroshilova sales
s.studenikin all

:warning: Логины в колонке login должны совпадать с логинами учётных записей в AW BI один в один. Регистр важен: «Ivanov» и «ivanov» — это разные значения для системы.

Файл 2: dashboard_catalog.xlsx

Каталог дашбордов с привязкой к департаменту. Именно из этой таблицы виджет возьмёт список кнопок для каждого пользователя. При масштабировании кейса это упростит в будущем добавление любого нового дашборда в список доступных, сразу с нужной категорией доступа.

department dashboard_name dashboard_url
hr Показатели HR https://aw-demo.ru/public/dashboard/HEMSqKCrerM2ZEMiFCMUGtHH527XoVIN
sales Продажи https://aw-demo.ru/app/dashboard/1140/view/3758
warehouse Динамика поступления на склады https://aw-demo.ru/share/dashboard/vK4dkvM7QfLptk7OHARtsZnBmYmowb4LK
all Показатели HR https://aw-demo.ru/public/dashboard/HEMSqKCrerM2ZEMiFCMUGtHH527XoVIN
all Продажи https://aw-demo.ru/app/dashboard/1140/view/3758
all Динамика поступления на склады https://aw-demo.ru/share/dashboard/vK4dkvM7QfLptk7OHARtsZnBmYmowb4LK

:bulb: Строки с department = all — это записи для руководства. Директор видит все дашборды, потому что в его атрибуте стоит all, а в таблице есть строки с all. Никаких специальных исключений в правилах прописывать не нужно — логика реализована через данные.


Шаг 2 — Настройка схемы доступов

Прежде чем использовать атрибут department в правилах — его нужно зарегистрировать в схеме доступов. Без этого шага атрибут не появится в выпадающем списке при настройке правил.

  1. Перейдите в Администрирование → Схемы доступов
  2. Нажмите «Добавить»
  3. Заполните форму:
    • Наименование: department
    • Алиас: Департамент
    • Тип: Строка
    • Галочку «Участвует в идентификации пользователя» не ставим
  4. Нажмите «Сохранить»


Шаг 3 — Модель user_permissions

user_permissions — встроенная модель AW BI. Создавать её не нужно, она уже есть в системе. Наша задача — наполнить её данными из подготовленного файла.

  1. Перейдите в раздел Модели (не забываем, что под учеткой технического администратора стенда)
  2. Найдите модель user_permissions (встроенная, есть у всех)
  3. Войдите в режим редактирования
  4. Добавьте источник данных — файл user_permissions.xlsx
  5. Нажмите «Загрузить данные в хранилище»


Шаг 4 — Маппинг провайдера

Данные загружены в user_permissions, но система ещё не знает, что поле department из этой модели — это тот самый атрибут department из схемы доступов. Эту связь устанавливает маппинг провайдера.

  1. Перейдите в Администрирование → Провайдеры
  2. Откройте внутренний провайдер AW (тип: AW user_permissions)
  3. Перейдите на вкладку «Маппинг схемы»
  4. Найдите строку с атрибутом department (левый столбец — атрибуты схемы)
  5. В правом столбце введите: department (имя поля из модели user_permissions)
  6. Нажмите «Сохранить»

:warning: Если маппинг не настроен — атрибут department не дойдёт до пользователя, и правила фильтрации будут работать вхолостую. Это самая частая причина пустых виджетов при внешне правильной настройке.


Шаг 5 — Модель dashboard_catalog и правило доступа

Теперь нам осталось буквально несколько шагов для работы нашей магии предварительных настроек. Делаем ту самую модель, в которой будет настроен доступ к строкам модели, на основе правил доступа.

Создаём модель

  1. Перейдите в раздел Модели → Добавить
  2. Выберите тип модели (в нашем случае логическая)
  3. Добавьте источник данных — файл dashboard_catalog.xlsx
  4. Отметьте поле dashboard_url как ссылку (ниже как это сделать и зачем необходимо)
  5. Загрузите данные в хранилище

Для чего помечаем поле dashboard_url как ссылку

Это важный шаг. Без него URL будет просто текстом, и навигация из виджета не заработает у нас должным образом.

  1. В режиме редактирования модели нажмите на поле dashboard_url
  2. Включите опцию «Является ссылкой»

Создаём правило доступа к строкам

  1. В редакторе модели dashboard_catalog нажмите кнопку «Настройки»
  2. Перейдите в раздел «Правила доступа»
  3. Нажмите «Создать правило»
  4. В блоке «Ограничения по данным (по полю модели)» настройте условие:
    • Поле модели: department
    • Тип: Строка
    • Оператор: =
    • Вид сравнения: Атрибут (не «Значение»!)
    • Атрибут: Департамент
  5. Нажмите «Сохранить»

:bulb: Вид сравнения «Атрибут» — ключевой момент. Это значит, что система будет сравнивать поле department каждой строки с атрибутом department конкретного пользователя динамически. Если выбрать «Значение» — система будет сравнивать с фиксированным текстом, и для всех будет одно и то же условие.

Раздел «Ограничения по пользователям» в нашем кейсе оставляем пустым. Правило применяется ко всем, а логика для руководства уже заложена в данных: строки с department = all отображаются пользователям с атрибутом all.


Шаг 6 — HTML-виджет

Теперь остался завершающий этап — сделать динамическое отображение наших изменяемых данных.

HTML-виджет с кодом ниже в данном случае — это лишь пример свободной интерпретации наших данных, показанный в рамках демонстрации примера. Ваши кнопки, логика отображения тут может быть абсолютно любой. Все упирается лишь в ваши навыки создания кастомных html-визуализаций.

Создаём виджет и добавляем поля

  1. Перейдите в Виджеты → Добавить → HTML-виджет
  2. Выберите модель dashboard_catalog как источник данных
  3. В настройках полей добавьте в «Столбцы» (без агрегации, плоская таблица):
    • dashboard_name
    • dashboard_url
  4. Вставьте код из трёх вкладок ниже
  5. Нажмите «Выполнить» для предпросмотра, затем «Опубликовать»


Вкладка HTML

<div class="nav-container">
  <div class="nav-header">
    <div class="nav-logo">
      <span class="logo-aw">AW</span><span class="logo-bi">BI</span>
    </div>
    <div class="nav-title">Мои дашборды</div>
    <div class="nav-subtitle">Доступные вам отчёты</div>
  </div>
  <div class="btn-list" id="btn-list"></div>
</div>

Вкладка CSS

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

* { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --pink: #e8445a;
  --teal: #2ec4b6;
  --bg: #f7f9fc;
  --card: #ffffff;
  --text: #1a1a2e;
  --subtext: #6b7280;
  --border: #eaecf0;
}

.nav-container {
  width: 100%; height: 100%;
  display: flex; flex-direction: column; gap: 16px;
  padding: 20px; background-color: var(--bg);
  border-radius: 16px; font-family: 'Inter', sans-serif;
  overflow-y: auto;
}

.nav-header {
  display: flex; flex-direction: column; gap: 4px;
  padding-bottom: 14px;
  border-bottom: 2px solid var(--border);
}

.nav-logo { display: flex; margin-bottom: 8px; }
.logo-aw { font-size: 1.3rem; font-weight: 700; color: var(--pink); }
.logo-bi { font-size: 1.3rem; font-weight: 700; color: var(--teal); }
.nav-title { font-size: 1.1rem; font-weight: 700; color: var(--text); }
.nav-subtitle { font-size: 0.8rem; color: var(--subtext); }

.btn-list { display: flex; flex-direction: column; gap: 8px; }

.nav-btn {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 12px 16px;
  background-color: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  font-family: 'Inter', sans-serif; font-size: 0.88rem; font-weight: 500;
  cursor: pointer; text-align: left;
  transition: all 0.2s ease;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}

.nav-btn:hover {
  background-color: var(--pink); color: #ffffff;
  border-color: var(--pink);
  transform: translateX(5px);
  box-shadow: 0 4px 12px rgba(232, 68, 90, 0.25);
}

.btn-arrow { margin-left: auto; opacity: 0.4; transition: opacity 0.2s; }
.nav-btn:hover .btn-arrow { opacity: 1; }
.empty-state { color: var(--subtext); font-size: 0.85rem; text-align: center; padding: 24px 0; }

Вкладка JS

function render() {
  const data = window.DATA.data;
  const btnList = document.getElementById('btn-list');
  btnList.innerHTML = '';

  if (!data || data.length === 0) {
    btnList.innerHTML = '<div class="empty-state">Нет доступных дашбордов</div>';
    return;
  }

  data.forEach(row => {
    const name = row.dashboard_name?.value || 'Без названия';

    // URL берём из agg_value — туда попадает ссылка,
    // когда на поле модели включена опция «Является ссылкой»
    const url = row.dashboard_url?.agg_value || row.dashboard_url?.value || '#';

    // Подбираем иконку по названию дашборда
    let icon = '📊';
    if (name.toLowerCase().includes('hr') ||
        name.toLowerCase().includes('персон')) icon = '👥';
    if (name.toLowerCase().includes('продаж')) icon = '📈';
    if (name.toLowerCase().includes('склад') ||
        name.toLowerCase().includes('поступ')) icon = '📦';

    const btn = document.createElement('button');
    btn.className = 'nav-btn';
    btn.innerHTML =
      `<span class="btn-icon">${icon}</span>` +
      `<span>${name}</span>` +
      `<span class="btn-arrow">→</span>`;

    // redirect() — встроенная функция AW BI для навигации из виджета.
    btn.addEventListener('click', () => { redirect(url); });

    btnList.appendChild(btn);
  });
}

Итог

Теперь у нас есть виджет, который открывается по-разному для каждого пользователя, а отображаемые ссылки на дашборды и кнопки соблюдают настройки безопасности в зависимости от отдела сотрудника.

Что мы настроили

  • Атрибут department в схеме доступов
  • Модель user_permissions с логинами и атрибутами пользователей
  • Маппинг провайдера: поле из модели → атрибут схемы
  • Модель dashboard_catalog с каталогом дашбордов
  • RLS-правило: показывать только строки, где department совпадает с атрибутом пользователя
  • HTML-виджет, который динамически рисует кнопки по данным из модели