Auto-generate and email passwords - admin never types/sees one
The admin password field is gone from both the create-user and
edit-user forms. Three flows now generate a password and mail it
instead of letting an admin type one:
- create_user(): password generated, welcome email sent
(login/password/portal link) right after the row commits
- new POST /api/admin/users/{id}/reset-password: generates a new
password, saves it, emails it - the only way a password changes now
- assign_rdp_slot(): sending a user their first grant on a pilot
(not every later tweak of an existing grant's expiry - guarded by
an is_new_grant check) sends a short "you have pilot access" email,
no credentials since the account already exists
If an email fails to send, the plaintext password comes back in the
API response as a one-time fallback so the admin isn't locked out of
handing it over by hand - the frontend only shows it then, never on
a successful send.
Added two small helpers (_build_light_email, _credentials_table) so
these three new templates share the light-theme card shell instead
of re-typing it; the existing access-approval emails keep their own
inline copies untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+26
-10
@@ -258,12 +258,12 @@
|
||||
<input id="u_first_name" placeholder="Имя" />
|
||||
<input id="u_last_name" placeholder="Фамилия" />
|
||||
<input id="u_exp" type="date" required />
|
||||
<input id="u_pwd" placeholder="new password (optional)" type="password" />
|
||||
<select id="u_active"><option value="true">active</option><option value="false">inactive</option></select>
|
||||
<select id="u_admin"><option value="false">user</option><option value="true">admin</option></select>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick="saveUser()">Save</button>
|
||||
<button id="user_reset_pwd_btn" onclick="resetUserPassword()" style="display:none;">🔑 Сбросить пароль</button>
|
||||
<button onclick="deleteUser()">Delete</button>
|
||||
<button onclick="clearUserForm()">Clear</button>
|
||||
</div>
|
||||
@@ -285,11 +285,11 @@
|
||||
|
||||
<hr>
|
||||
<div class="list-title">Добавить пользователя</div>
|
||||
<div class="field-help">Пароль не задаётся вручную — он сгенерируется и уйдёт пользователю на почту (username) сразу после создания.</div>
|
||||
<div class="form-grid">
|
||||
<input id="new_u_name" placeholder="username" />
|
||||
<input id="new_u_name" placeholder="username (email)" />
|
||||
<input id="new_u_first_name" placeholder="Имя" />
|
||||
<input id="new_u_last_name" placeholder="Фамилия" />
|
||||
<input id="new_u_pwd" placeholder="password" type="password" />
|
||||
<input id="new_u_exp" type="date" required />
|
||||
<select id="new_u_active"><option value="true">active</option><option value="false">inactive</option></select>
|
||||
<select id="new_u_admin"><option value="false">user</option><option value="true">admin</option></select>
|
||||
@@ -960,9 +960,9 @@
|
||||
document.getElementById('u_first_name').value = firstName || '';
|
||||
document.getElementById('u_last_name').value = lastName || '';
|
||||
document.getElementById('u_exp').value = dateFromIso(expiresIso);
|
||||
document.getElementById('u_pwd').value = '';
|
||||
document.getElementById('u_active').value = String(active);
|
||||
document.getElementById('u_admin').value = String(isAdmin);
|
||||
document.getElementById('user_reset_pwd_btn').style.display = '';
|
||||
markSelected('.user-item', 'data-user-id', id);
|
||||
syncAclForSelectedUser();
|
||||
}
|
||||
@@ -970,15 +970,17 @@
|
||||
async function createUser() {
|
||||
const expDate = document.getElementById('new_u_exp').value;
|
||||
if (!expDate) return alert('Выберите дату деактивации');
|
||||
await api('/api/admin/users', 'POST', {
|
||||
const result = await api('/api/admin/users', 'POST', {
|
||||
username: document.getElementById('new_u_name').value,
|
||||
first_name: document.getElementById('new_u_first_name').value,
|
||||
last_name: document.getElementById('new_u_last_name').value,
|
||||
password: document.getElementById('new_u_pwd').value,
|
||||
expires_at: expiryToApi(expDate),
|
||||
active: document.getElementById('new_u_active').value === 'true',
|
||||
is_admin: document.getElementById('new_u_admin').value === 'true',
|
||||
});
|
||||
if (result.password) {
|
||||
alert(`Письмо не отправлено (${result.email_status}). Пароль: ${result.password}\nПередайте его пользователю вручную.`);
|
||||
}
|
||||
location.reload();
|
||||
}
|
||||
|
||||
@@ -995,12 +997,22 @@
|
||||
active: document.getElementById('u_active').value === 'true',
|
||||
is_admin: document.getElementById('u_admin').value === 'true',
|
||||
};
|
||||
const pwd = document.getElementById('u_pwd').value.trim();
|
||||
if (pwd) payload.password = pwd;
|
||||
await api(`/api/admin/users/${id}`, 'PUT', payload);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
async function resetUserPassword() {
|
||||
const id = document.getElementById('u_id').value;
|
||||
if (!id) return alert('Выберите пользователя');
|
||||
if (!confirm('Сгенерировать новый пароль и отправить его пользователю на почту?')) return;
|
||||
const result = await api(`/api/admin/users/${id}/reset-password`, 'POST', {});
|
||||
if (result.password) {
|
||||
alert(`Письмо не отправлено (${result.email_status}). Новый пароль: ${result.password}\nПередайте его пользователю вручную.`);
|
||||
} else {
|
||||
alert('Новый пароль отправлен пользователю на почту.');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser() {
|
||||
const id = document.getElementById('u_id').value;
|
||||
if (!id) return alert('Выберите пользователя');
|
||||
@@ -1010,9 +1022,10 @@
|
||||
}
|
||||
|
||||
function clearUserForm() {
|
||||
['u_id','u_name','u_exp','u_pwd'].forEach(id => document.getElementById(id).value = '');
|
||||
['u_id','u_name','u_exp'].forEach(id => document.getElementById(id).value = '');
|
||||
document.getElementById('u_active').value = 'true';
|
||||
document.getElementById('u_admin').value = 'false';
|
||||
document.getElementById('user_reset_pwd_btn').style.display = 'none';
|
||||
document.querySelectorAll('.acl_service').forEach(x => x.checked = false);
|
||||
document.querySelectorAll('.user-item').forEach((el) => el.classList.remove('selected-item'));
|
||||
}
|
||||
@@ -1283,7 +1296,10 @@
|
||||
const userId = document.getElementById(`pilot_slot_user_${slotId}`).value;
|
||||
const expInput = document.getElementById(`pilot_slot_exp_${slotId}`);
|
||||
const expires_at = (userId && expInput.value) ? `${expInput.value}T23:59:59+00:00` : null;
|
||||
await api(`/api/admin/rdp-slots/${slotId}/assign`, 'PUT', {user_id: userId || null, expires_at});
|
||||
const result = await api(`/api/admin/rdp-slots/${slotId}/assign`, 'PUT', {user_id: userId || null, expires_at});
|
||||
if (result.email_status && !result.email_status.startsWith('Email отправлен')) {
|
||||
alert(`Слот назначен, но письмо пользователю не ушло: ${result.email_status}`);
|
||||
}
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user