Пользователи в настройках — как в мастере: простой вид и расширенный под раскрытием
- Блок «Учётные записи» мастера вынесен в part_accounts.html (accountsAdd, accountsAD) и стоит и в мастере, и в Настройка → Пользователи: роли плитками, создать сотрудника (с модулями), сотрудники и группы из домена. - Список сотрудников: строка — кружок, имя, логин, источник, последний вход, роль; «Изменить» раскрывает правку (имя, e-mail, роль, пароль, модули, «может входить»), «Удалить сотрудника» — тихой ссылкой с подтверждением. - «Расширенные настройки» под раскрытием: роли и права, домен вручную (контроллер, Base DN, сертификат ЦС, служебная запись, группы → роли, роль по умолчанию, время сессии), проверка входа, перепроверка групп; подписи укорочены, поля в ряд. - Ссылки-кнопки (dq-link, «убрать») одинаковые в настройках и мастере. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -107,51 +107,60 @@
|
||||
<section class="settings-section" id="sec-users">
|
||||
{{$U := .Users}}{{$A := .Settings.Auth}}
|
||||
<h1>Пользователи и вход</h1>
|
||||
<p class="muted">Кто может входить в bj и что ему разрешено. Вход — локальной учётной записью или доменной (Active Directory), роль доменного пользователя назначается по его группам AD.</p>
|
||||
<p class="muted">Кто может входить в bj и что ему разрешено. Сотрудник входит своей учётной записью bj или доменной (Active Directory) — тогда пароль проверяет домен.</p>
|
||||
{{if $U.Open}}<div class="card callout" style="padding:10px 14px;margin-bottom:8px;background:color-mix(in srgb,var(--warn) 10%,var(--card));display:flex;gap:8px;align-items:center"><span class="dot warn"></span><span><b>Вход пока не требуется:</b> пользователи не созданы. Создайте первого администратора — после этого все страницы потребуют входа.</span></div>{{end}}
|
||||
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if $U.Open}}warn{{else}}ok{{end}}"></span>Роли</h2>
|
||||
<h2>Сотрудники ({{len $U.Users}})</h2>
|
||||
{{if $U.Users}}<div class="usr-list">
|
||||
{{range $U.Users}}
|
||||
<details class="usr">
|
||||
<summary>
|
||||
<span class="usr-av" aria-hidden="true">{{avatarLetter (or .Name .Login)}}</span>
|
||||
<span class="usr-main"><b>{{if .Name}}{{.Name}}{{else}}{{.Login}}{{end}}</b><small>{{.Login}}{{if .Email}} · {{.Email}}{{end}} · {{if eq .Source "ad"}}доменная учётная запись{{else}}пароль в bj{{end}} · {{if not .LastLogin.IsZero}}вход {{msk .LastLogin}}{{else}}ещё не входил{{end}}</small></span>
|
||||
{{if not .Active}}<span class="badge rejected">заблокирован</span>{{end}}
|
||||
<span class="usr-role">{{if $U.RoleShown .Role}}{{roleTitle .Role}}{{else}}{{$U.RoleNote .Role}}{{end}}</span>
|
||||
<span class="usr-edit">Изменить</span>
|
||||
</summary>
|
||||
<form method="post" action="/admin/setup/users" class="usr-form">
|
||||
<input type="hidden" name="login" value="{{.Login}}"><input type="hidden" name="source" value="{{.Source}}">
|
||||
<label>Имя<input type="text" name="name" value="{{.Name}}" autocomplete="off"></label>
|
||||
<label>E-mail<input type="text" name="email" value="{{.Email}}" autocomplete="off"></label>
|
||||
<label>Роль<select name="role">{{$r := .Role}}{{if not ($U.RoleShown $r)}}<option value="{{$r}}" selected>{{$U.RoleNote $r}}</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $r}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||||
{{if eq .Source "local"}}<label>Новый пароль<input type="password" name="password" placeholder="пусто — оставить прежний" autocomplete="new-password"></label>{{else}}<div class="usr-note">Пароль проверяет домен{{if .ADGroups}} · группы: {{range $i,$g := .ADGroups}}{{if $i}}, {{end}}{{$g}}{{end}}{{end}}{{if .Note}} · {{.Note}}{{end}}</div>{{end}}
|
||||
<div class="usr-checks">{{$m := .Modules}}{{range $U.Modules}}<label><input type="checkbox" name="modules" value="{{.Key}}" {{if has $m .Key}}checked{{end}}>{{.Title}}</label>{{end}}
|
||||
<label><input type="checkbox" name="active" value="1" {{if .Active}}checked{{end}}>может входить</label></div>
|
||||
<div class="usr-act"><button class="btn" type="submit">Сохранить</button>
|
||||
<button class="usr-del" type="submit" formaction="/admin/setup/users/delete" onclick="return confirm('Удалить сотрудника {{.Login}}? Войти он больше не сможет.')">Удалить сотрудника</button></div>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}</div>{{else}}<p class="muted">Пока никого — добавьте сотрудника ниже.</p>{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>{{if $U.Open}}Создать первого администратора{{else}}Добавить сотрудника{{end}}</h2>
|
||||
{{template "accountsAdd" (withRet . "/admin/setup")}}
|
||||
{{template "accountsAD" (withRet . "/admin/setup")}}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Активные сессии ({{len $U.Sessions}})</h2>
|
||||
{{if $U.Sessions}}<table style="font-size:12.5px"><thead><tr><th>Пользователь</th><th>Вход</th><th>Активность</th><th>Адрес</th><th>Браузер</th><th></th></tr></thead><tbody>
|
||||
{{range $U.Sessions}}<tr><td><b>{{.Login}}</b></td><td>{{msk .Created}}</td><td>{{msk .Seen}}</td><td><code>{{.IP}}</code></td><td class="muted" style="font-size:11px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{.UA}}</td>
|
||||
<td><form method="post" action="/admin/setup/users/session-drop"><input type="hidden" name="id" value="{{.ID}}"><button class="btn" type="submit">Завершить</button></form></td></tr>{{end}}
|
||||
</tbody></table>{{else}}<p class="muted">Сессий нет.</p>{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<details class="adv-box">
|
||||
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">роли и права, домен вручную: контроллер, сертификат, группы, перепроверка, время сессии</span></summary>
|
||||
<h3 class="adv-h">Роли и права</h3>
|
||||
<table style="font-size:12.5px"><thead><tr><th>Роль</th><th>Что разрешено</th></tr></thead><tbody>
|
||||
{{range $U.Roles}}<tr><td style="white-space:nowrap"><b>{{.Title}}</b><br><code style="font-size:11px">{{.Key}}</code></td><td>{{.Desc}}</td></tr>{{end}}
|
||||
{{range $U.Hidden}}<tr class="muted"><td style="white-space:nowrap">{{.Title}}</td><td>Недоступна: в лицензии нет модуля «{{.Missing}}».</td></tr>{{end}}
|
||||
</tbody></table>
|
||||
<p class="muted" style="font-size:12px;margin-top:6px">{{if $U.Modules}}Подключённые модули ({{range $i, $m := $U.Modules}}{{if $i}}, {{end}}«{{$m.Title}}»{{end}}) можно выдать любому пользователю дополнительно к роли. {{end}}Роли и модули показываются по лицензии: права модуля, которого нет в лицензии, не действуют. Настройки и пользователи — только администратор.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Пользователи ({{len $U.Users}})</h2>
|
||||
{{range $U.Users}}
|
||||
<form method="post" action="/admin/setup/users" style="display:grid;grid-template-columns:1.1fr 1.4fr 1.2fr 1fr auto;gap:8px;align-items:end;padding:10px 0;border-bottom:1px solid var(--border)">
|
||||
<input type="hidden" name="login" value="{{.Login}}"><input type="hidden" name="source" value="{{.Source}}">
|
||||
<div><div style="font-weight:600">{{.Login}} {{if eq .Source "ad"}}<span class="badge" style="font-size:10px">AD</span>{{end}}{{if not .Active}}<span class="badge rejected" style="font-size:10px">заблокирован</span>{{end}}</div>
|
||||
<div class="muted" style="font-size:11px">{{if not .LastLogin.IsZero}}вход {{msk .LastLogin}}{{else}}ещё не входил{{end}}{{if .Note}} · {{.Note}}{{end}}</div>
|
||||
{{if .ADGroups}}<div class="muted" style="font-size:11px">группы: {{range $i,$g := .ADGroups}}{{if $i}}, {{end}}{{$g}}{{end}}</div>{{end}}</div>
|
||||
<label style="margin:0;font-size:11px">Имя <input type="text" name="name" value="{{.Name}}" style="width:100%"><span class="muted">E-mail</span> <input type="text" name="email" value="{{.Email}}" style="width:100%"></label>
|
||||
<label style="margin:0;font-size:11px">Роль <select name="role" style="width:100%">{{$r := .Role}}{{if not ($U.RoleShown $r)}}<option value="{{$r}}" selected>{{$U.RoleNote $r}}</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $r}}selected{{end}}>{{.Title}}</option>{{end}}</select>
|
||||
<div style="margin-top:4px">{{$m := .Modules}}{{range $U.Modules}}<label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0 8px 0 0;font-size:11px"><input type="checkbox" name="modules" value="{{.Key}}" {{if has $m .Key}}checked{{end}}>{{.Title}}</label>{{end}}</div></label>
|
||||
<label style="margin:0;font-size:11px">{{if eq .Source "local"}}Новый пароль <input type="password" name="password" placeholder="оставьте пустым" autocomplete="new-password" style="width:100%">{{else}}<span class="muted">пароль проверяет AD</span>{{end}}
|
||||
<div style="margin-top:4px"><label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0;font-size:11px"><input type="checkbox" name="active" value="1" {{if .Active}}checked{{end}}>активен</label></div></label>
|
||||
<div style="display:flex;gap:6px"><button class="btn btn-ok" type="submit">Сохранить</button><button class="btn" type="submit" formaction="/admin/setup/users/delete" onclick="return confirm('Удалить пользователя {{.Login}}?')">Удалить</button></div>
|
||||
</form>
|
||||
{{else}}<p class="muted">Пользователей нет.</p>{{end}}
|
||||
|
||||
<h3 style="margin:16px 0 6px">{{if $U.Open}}Создать первого администратора{{else}}Добавить пользователя{{end}}</h3>
|
||||
<form method="post" action="/admin/setup/users" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;max-width:900px;align-items:end">
|
||||
<label>Логин <input type="text" name="login" placeholder="ivanov" required style="width:100%"></label>
|
||||
<label>Имя <input type="text" name="name" placeholder="Иванов И.И." style="width:100%"></label>
|
||||
<label>Роль <select name="role" style="width:100%">{{range $U.Roles}}<option value="{{.Key}}" {{if and $U.Open (eq .Key "admin")}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||||
<label>Источник <select name="source" style="width:100%" {{if $U.Open}}disabled{{end}}><option value="local">Локальный (пароль в bj)</option><option value="ad" {{if not $A.ADEnabled}}disabled{{end}}>Active Directory</option></select></label>
|
||||
<label>Пароль (для локального, от 8 символов) <input type="password" name="password" autocomplete="new-password" style="width:100%"></label>
|
||||
<label>E-mail <input type="text" name="email" style="width:100%"></label>
|
||||
{{if $U.Modules}}<label>Модули <div>{{range $U.Modules}}<label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0 8px 0 0;font-size:12px"><input type="checkbox" name="modules" value="{{.Key}}">{{.Title}}</label>{{end}}</div></label>{{end}}
|
||||
<div><input type="hidden" name="active" value="1"><button class="btn btn-ok" type="submit">{{if $U.Open}}Создать администратора{{else}}Добавить{{end}}</button></div>
|
||||
</form>
|
||||
<p class="muted" style="font-size:12px;margin-top:8px">Доменного пользователя можно заранее завести с источником «Active Directory», чтобы выдать ему модули; роль при входе возьмётся из групп AD. Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if $A.ADEnabled}}ok{{else}}warn{{end}}"></span>Вход через Active Directory</h2>
|
||||
<h3 class="adv-h">Вход через Active Directory</h3>
|
||||
<p class="muted">bj подключается к контроллеру домена и проверяет пароль пользователя (LDAP bind его же учётной записью). Роль назначается по членству в группах AD, включая вложенные. Пользователь появляется в списке при первом входе. Чтобы изменения групп действовали без повторного входа, укажите сервисную учётную запись только на чтение: bj будет перечитывать группы доменных пользователей по расписанию, менять роль и снимать доступ у удалённых из групп.</p>
|
||||
<form method="post" action="/admin/setup/auth" style="display:grid;gap:10px;max-width:820px;margin-top:10px">
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="ad_enabled" value="1" {{if $A.ADEnabled}}checked{{end}}> Разрешить вход доменной учётной записью</label>
|
||||
@@ -159,19 +168,19 @@
|
||||
<label>Контроллер домена <input type="text" name="ad_server" value="{{$A.ADServer}}" placeholder="ldaps://dc1.corp.example.ru:636" style="width:100%"></label>
|
||||
<label>Домен (суффикс UPN) <input type="text" name="ad_domain" value="{{$A.ADDomain}}" placeholder="corp.example.ru" style="width:100%"></label>
|
||||
</div>
|
||||
<label>База поиска (Base DN) <span class="muted">пусто = взять с контроллера</span> <input type="text" name="ad_base_dn" value="{{$A.ADBaseDN}}" placeholder="DC=corp,DC=example,DC=ru" style="width:100%"></label>
|
||||
<label>База поиска (Base DN) <input type="text" name="ad_base_dn" value="{{$A.ADBaseDN}}" placeholder="пусто — возьмётся с контроллера" style="width:100%"></label>
|
||||
<div style="display:flex;gap:18px;flex-wrap:wrap">
|
||||
<label style="display:flex;gap:6px;align-items:center;font-weight:400;margin:0"><input type="checkbox" name="ad_starttls" value="1" {{if $A.ADStartTLS}}checked{{end}}> StartTLS для ldap://</label>
|
||||
<label style="display:flex;gap:6px;align-items:center;font-weight:400;margin:0"><input type="checkbox" name="ad_skip_verify" value="1" {{if $A.ADSkipVerify}}checked{{end}}> Не проверять сертификат контроллера</label>
|
||||
</div>
|
||||
<label>Сертификат корпоративного ЦС (PEM), если проверяем <textarea name="ad_ca_pem" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:11px" placeholder="-----BEGIN CERTIFICATE-----">{{$A.ADCAPEM}}</textarea></label>
|
||||
<label>Сертификат центра сертификации домена (PEM) <textarea name="ad_ca_pem" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:11px" placeholder="-----BEGIN CERTIFICATE-----">{{$A.ADCAPEM}}</textarea></label>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||||
<label>Сервисная учётная запись <span class="muted">для перепроверки групп, только чтение</span> <input type="text" name="ad_bind_user" value="{{$A.ADBindUser}}" placeholder="svc-bj@corp.example.ru" autocomplete="off" style="width:100%"></label>
|
||||
<label>Пароль сервисной учётной записи <input type="password" name="ad_bind_password" placeholder="{{if $A.ADBindPassword}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль{{end}}" autocomplete="new-password" style="width:100%"></label>
|
||||
<label>Служебная учётная запись <input type="text" name="ad_bind_user" value="{{$A.ADBindUser}}" placeholder="svc-bj@corp.example.ru" autocomplete="off" style="width:100%"></label>
|
||||
<label>Её пароль <input type="password" name="ad_bind_password" placeholder="{{if $A.ADBindPassword}}задан — пусто, чтобы не менять{{else}}пароль{{end}}" autocomplete="new-password" style="width:100%"></label>
|
||||
<label>Перепроверять группы, минут <input type="number" name="ad_recheck_minutes" min="5" max="1440" value="{{if $A.ADRecheckMinutes}}{{$A.ADRecheckMinutes}}{{else}}15{{end}}" style="width:100%"></label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600;margin-bottom:4px">Группы AD → роли <span class="muted" style="font-weight:400;font-size:12px">(имя группы или полный DN; первое совпадение сверху)</span></div>
|
||||
<div style="font-weight:600;font-size:13.5px;margin:4px 0 6px">Группы домена → роли <span class="muted" style="font-weight:400;font-size:12.5px">имя группы или полный DN; действует первое совпадение сверху</span></div>
|
||||
<div id="ad-groups" style="display:grid;gap:6px">
|
||||
{{range $A.ADGroups}}<div style="display:grid;grid-template-columns:2fr 1fr;gap:8px"><input type="text" name="ad_group" value="{{.Group}}" placeholder="BJ-Operators"><select name="ad_role">{{$r := .Role}}{{if not ($U.RoleShown $r)}}<option value="{{$r}}" selected>{{$U.RoleNote $r}} (группа не действует)</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $r}}selected{{end}}>{{.Title}}</option>{{end}}</select></div>{{end}}
|
||||
{{range $i := list "1" "2"}}<div style="display:grid;grid-template-columns:2fr 1fr;gap:8px"><input type="text" name="ad_group" value="" placeholder="имя группы AD"><select name="ad_role">{{range $U.Roles}}<option value="{{.Key}}">{{.Title}}</option>{{end}}</select></div>{{end}}
|
||||
@@ -181,30 +190,23 @@
|
||||
<label>Роль, если ни одна группа не совпала <select name="ad_default_role" style="width:100%"><option value="">отказать во входе</option>{{if and $A.ADDefaultRole (not ($U.RoleShown $A.ADDefaultRole))}}<option value="{{$A.ADDefaultRole}}" selected>{{$U.RoleNote $A.ADDefaultRole}} (вход будет отклонён)</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $A.ADDefaultRole}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||||
<label>Выход при бездействии, часов <input type="number" name="session_hours" min="1" max="24" value="{{if $A.SessionHours}}{{$A.SessionHours}}{{else}}8{{end}}" style="width:100%"></label>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button class="btn btn-ok" type="submit">Сохранить</button></div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button class="btn" type="submit">Сохранить</button></div>
|
||||
</form>
|
||||
{{with adDraft}}{{template "adTrustCard" (withRet . "")}}{{end}}
|
||||
<form method="post" action="/admin/setup/auth/test" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;max-width:820px;margin-top:12px;align-items:end">
|
||||
<label>Проверка: логин домена <input type="text" name="test_login" placeholder="ivanov" style="width:100%" autocomplete="off"></label>
|
||||
<label>Проверить вход: логин домена <input type="text" name="test_login" placeholder="ivanov" style="width:100%" autocomplete="off"></label>
|
||||
<label>Пароль <input type="password" name="test_password" style="width:100%" autocomplete="off"></label>
|
||||
<button class="btn" type="submit">Проверить AD</button>
|
||||
<button class="btn btn-secondary" type="submit">Проверить</button>
|
||||
</form>
|
||||
<p class="muted" style="font-size:12px;margin-top:6px">Без логина и пароля проверяется только доступность контроллера. С логином — вход, группы и вычисленная роль. Пароль проверки никуда не сохраняется.</p>
|
||||
{{$R := $U.ADRecheck}}
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px;font-size:12.5px">
|
||||
{{if and $A.ADEnabled $A.ADBindUser $A.ADBindPassword}}
|
||||
<span><span class="dot {{if $R.Err}}err{{else if $R.At.IsZero}}warn{{else}}ok{{end}}"></span>Перепроверка групп каждые {{if $A.ADRecheckMinutes}}{{$A.ADRecheckMinutes}}{{else}}15{{end}} мин: {{if $R.At.IsZero}}ещё не выполнялась{{else}}последняя {{msk $R.At}}, проверено {{$R.Checked}}, изменений {{$R.Changed}}{{if $R.Err}} — <b>ошибка:</b> {{$R.Err}}{{end}}{{end}}</span>
|
||||
<form method="post" action="/admin/setup/auth/recheck"><button class="btn" type="submit">Перепроверить сейчас</button></form>
|
||||
<form method="post" action="/admin/setup/auth/recheck"><button class="btn btn-secondary" type="submit">Перепроверить сейчас</button></form>
|
||||
{{else}}<span class="muted"><span class="dot warn"></span>Перепроверка групп выключена: роль доменного пользователя меняется только при следующем входе. Укажите сервисную учётную запись выше.</span>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Активные сессии ({{len $U.Sessions}})</h2>
|
||||
{{if $U.Sessions}}<table style="font-size:12.5px"><thead><tr><th>Пользователь</th><th>Вход</th><th>Активность</th><th>Адрес</th><th>Браузер</th><th></th></tr></thead><tbody>
|
||||
{{range $U.Sessions}}<tr><td><b>{{.Login}}</b></td><td>{{msk .Created}}</td><td>{{msk .Seen}}</td><td><code>{{.IP}}</code></td><td class="muted" style="font-size:11px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{.UA}}</td>
|
||||
<td><form method="post" action="/admin/setup/users/session-drop"><input type="hidden" name="id" value="{{.ID}}"><button class="btn" type="submit">Завершить</button></form></td></tr>{{end}}
|
||||
</tbody></table>{{else}}<p class="muted">Сессий нет.</p>{{end}}
|
||||
<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user