diff --git a/docs/plan-simplify-2026-06-11.md b/docs/plan-simplify-2026-06-11.md
new file mode 100644
index 0000000..0d70d0c
--- /dev/null
+++ b/docs/plan-simplify-2026-06-11.md
@@ -0,0 +1,85 @@
+# План упрощения — 2026-06-11
+
+## Проблема
+
+AI усложнил код `entries.js` фоллбеками `getOrCreateCompany`, запутанной логикой `ownCo`,
+ненужными редиректами `?company=`. Пользователь указал что логика простая и не требует усложнений.
+
+## Исходные данные (что юзер знает после входа по Keycloak)
+
+1. **email** — из JWT (Keycloak claims)
+2. **session.token** — access_token от Keycloak
+3. **IAM API** (`fetchIamUser`) даёт:
+ - `email`
+ - `profiles[]` — список компаний юзера: `[{id, client_id, company_name, is_active_profile}, ...]`
+ - `activeProfile` — текущая активная компания
+ - `isAdmin` — флаг администратора
+
+## Базовые правила
+
+- **Компания** = общий список IP. Все юзеры компании видят одни и те же записи и могут CRUD.
+- **email** пишется в `audit_log.created_by` — кто сделал CRUD.
+- **client_id** (WZ01112, WZ03709…) — идентификатор компании в БД (`companies.client_id`).
+
+## Переключение компаний (мульти-компания)
+
+Если у юзера `profiles.length > 1` — он выбирает активную компанию через переключатель.
+CRUD идёт в активную компанию.
+
+## Админ-режим (чекбокс → toggle)
+
+### Правило (финальное):
+
+> Toggle «Администратор» показывается **только** когда:
+> `isAdmin === true` **И** `activeClientId === 'WZ01112'`
+
+### Временное исключение (тесты):
+
+Пока я (WZ01112 + ntazetdinov@nubes.ru) — тоже вижу toggle, даже без `isAdmin`.
+Убрать когда перейдём на чистый IAM `isAdmin`.
+
+### Поведение toggle:
+
+- **ВЫКЛ** — обычный режим: юзер видит свою активную компанию, CRUD только в неё
+- **ВКЛ** — admin-режим: выпадающий список ВСЕХ компаний, можно выбрать любую, ссылки «Аудит» и «Лимиты»
+
+### Реализация toggle:
+
+- Не `` с `onchange="this.form.submit()"` (старый вариант, не работал)
+- Замена на **toggle-переключатель** (как ползунок ВКЛ/ВЫКЛ)
+- `onchange` → `fetch('/toggle-admin', {method:'POST'})` → `location.reload()`
+- Никакой формы, никакого CSRF — просто JS fetch и перезагрузка страницы
+
+## Что меняем (4 файла)
+
+### 1. `ui/index.js` — resolveUser middleware
+
+- Добавить `canAdminMode`:
+ ```js
+ canAdminMode: (iamAdmin && activeClientId === 'WZ01112') || activeClientId === 'WZ01112' // временно
+ ```
+- Убрать `isAdmin: iamAdmin || activeClientId === ADMIN_CLIENT_ID` — заменить на чистое `isAdmin: iamAdmin`
+- `adminMode` остаётся из сессии
+
+### 2. `views/index.ejs` — header
+
+- Условие показа toggle: `<% if (canAdminMode) { %>` вместо `<% if (isAdmin) { %>`
+- Заменить checkbox на toggle
+- Ссылки «Аудит»/«Лимиты» — по `adminMode` (уже так)
+
+### 3. `ui/routes/entries.js` — GET /
+
+- **Убрать** `getOrCreateCompany`-фоллбек (строки 89-96)
+- **Упростить** admin-ветку: если нет `?company=` — показать свою компанию
+- Обычный пользователь: без изменений (уже работает)
+
+### 4. `ui/routes/auth.js` — POST /toggle-admin
+
+- Без изменений (уже работает — переключает `req.session.adminMode`)
+
+## Почему именно так
+
+1. **AND а не OR**: админ не должен видеть toggle в чужих компаниях — только в WZ01112
+2. **Toggle а не checkbox**: чекбокс = «прочитано/не прочитано», toggle = «режим ВКЛ/ВЫКЛ» — семантически верно
+3. **JS fetch а не form submit**: быстрее, без моргания страницы, без CSRF-проблем
+4. **Никаких фоллбеков**: компания либо есть в списке (из IAM), либо юзер к ней не относится — не надо «создавать если нет»
diff --git a/package.json b/package.json
index ed0f5ac..f797ade 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ipwhitelist",
- "version": "0.5.40",
+ "version": "0.5.41",
"description": "IP WhiteList microservice for cloud provider",
"main": "server.js",
"scripts": {
diff --git a/ui/index.js b/ui/index.js
index 437e1a4..bc34c0a 100644
--- a/ui/index.js
+++ b/ui/index.js
@@ -58,8 +58,11 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) {
? sessionActive
: (allClientIds[0] || rawClientId);
- // 🔮 isAdmin через IAM, временно: || clientId === WZ01112
+ // 🔮 isAdmin через IAM
const iamAdmin = req.session && req.session.user && req.session.user.isAdmin;
+ // ВРЕМЕННО: WZ01112 без isAdmin тоже видит toggle (для тестов)
+ const canAdminMode = !!(iamAdmin && activeClientId === ADMIN_CLIENT_ID)
+ || activeClientId === ADMIN_CLIENT_ID; // ← убрать после тестов
req.user = {
clientId: rawClientId,
allClientIds,
@@ -67,7 +70,8 @@ function createUiRouter({ auth, MOCK_USERS, authLimiter }) {
companyId: payload.company_id || payload.companyId || null,
companyName: payload.company_name || payload.companyName || activeClientId,
email: payload.email || activeClientId + '@unknown',
- isAdmin: iamAdmin || activeClientId === ADMIN_CLIENT_ID,
+ isAdmin: iamAdmin,
+ canAdminMode,
adminMode: !!(req.session && req.session.adminMode),
};
} catch (e) {
diff --git a/ui/routes/entries.js b/ui/routes/entries.js
index 0d9ded2..805f983 100644
--- a/ui/routes/entries.js
+++ b/ui/routes/entries.js
@@ -83,29 +83,17 @@ function createRouter() {
const companyId = req.query.company ? parseInt(req.query.company, 10) : null;
- if (!companyId) {
- // Админ без ?company= — показываем свою компанию
- let ownCo = companies.find(c => c.client_id === req.user.clientId) || null;
- if (!ownCo) {
- await api.get('/api/v1/entries', token); // создаст компанию через getOrCreateCompany
- const cr2 = await api.get('/api/v1/companies', token);
- ownCo = (cr2.data.companies || []).find(c => c.client_id === req.user.clientId) || null;
- }
- if (ownCo) {
- const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : '';
- const er = await api.get('/api/v1/entries?company=' + ownCo.id + includeDeleted, token);
- entries = er.data.entries || [];
- limit = er.data.limit || 15;
- selectedCompany = ownCo;
- }
- } else {
+ if (companyId) {
selectedCompany = companies.find(c => c.id === companyId) || null;
- if (selectedCompany) {
- const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : '';
- const er = await api.get('/api/v1/entries?company=' + companyId + includeDeleted, token);
- entries = er.data.entries || [];
- limit = er.data.limit || 15;
- }
+ }
+ // Если компания не выбрана — показываем список компаний, без записей
+ // (админ выбирает компанию из выпадающего списка)
+
+ if (selectedCompany) {
+ const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : '';
+ const er = await api.get('/api/v1/entries?company=' + selectedCompany.id + includeDeleted, token);
+ entries = er.data.entries || [];
+ limit = er.data.limit || 15;
}
} else {
// Обычный пользователь: запрашиваем записи для активной компании
diff --git a/views/index.ejs b/views/index.ejs
index 3fd9420..0ab5bee 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -159,13 +159,15 @@
Белые списки IP v<%= appVersion %>
- <% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
-
+ <% if (typeof canAdminMode !== 'undefined' && canAdminMode) { %>
+
+
+ style="display:none;">
+
+
+
+
<% } %>
<% if (typeof adminMode !== 'undefined' && adminMode) { %>
Аудит