diff --git a/app/templates/admin.html b/app/templates/admin.html index 3955f09..2f69c47 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -241,20 +241,25 @@
Список пользователей
+ {% for u in users %} - +
+
+
{{u.username}}{% if u.first_name or u.last_name %} — {{ (u.first_name + ' ' + u.last_name)|trim }}{% endif %}{% if not u.active %} выключен{% endif %}
+ +
+ +
{% endfor %}
-
Редактирование пользователя
+
Новый пользователь
+
Пароль не задаётся вручную — он сгенерируется и уйдёт пользователю на почту (username) сразу после создания.
- + @@ -264,11 +269,11 @@
- +
-
+ - -
-
Добавить пользователя
-
Пароль не задаётся вручную — он сгенерируется и уйдёт пользователю на почту (username) сразу после создания.
-
- - - - - - -
-
@@ -331,22 +323,28 @@
Список WEB
+ {% for s in web_services %} - + +
+ {% else %} +
WEB-сервисов пока нет
{% endfor %}
-
Редактирование WEB
+
Новый WEB-сервис
Для пользователей показывается только название и описание. Технический тип скрыт.
@@ -395,7 +393,7 @@
- +
@@ -422,7 +420,7 @@
-
+
- -
-
Добавить WEB
-
Горячий пул задается сверху и общий для всех WEB сервисов.
-
- - - - - - - - - -
-
Категории
-
- {% for c in categories %} - - {% endfor %} -
- @@ -483,22 +444,28 @@
Список RDP
+ {% for s in rdp_services %} - + +
+ {% else %} +
RDP-сервисов пока нет
{% endfor %}
-
Редактирование RDP
+
Новый RDP-сервис
Используйте поля host/port/user. Поле target собирается автоматически.
@@ -529,8 +496,8 @@
- - + +
@@ -556,7 +523,7 @@
-
+ - -
-
Добавить RDP
-
Для большинства кейсов достаточно host + user + password.
-
- - - - - - - - - - - - - -
-
Категории
-
- {% for c in categories %} - - {% endfor %} -
-
@@ -639,7 +575,7 @@ - + {% else %}
Пилотов пока нет
@@ -963,30 +899,21 @@ document.getElementById('u_active').value = String(active); document.getElementById('u_admin').value = String(isAdmin); document.getElementById('user_reset_pwd_btn').style.display = ''; + document.getElementById('user_delete_btn').style.display = ''; + document.getElementById('user_acl_box').style.display = ''; + document.getElementById('user_pwd_help').style.display = 'none'; + document.getElementById('user_form_title').textContent = username; markSelected('.user-item', 'data-user-id', id); syncAclForSelectedUser(); } - async function createUser() { - const expDate = document.getElementById('new_u_exp').value; - if (!expDate) return alert('Выберите дату деактивации'); - 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, - 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(); + function startNewUser() { + clearUserForm(); + document.getElementById('u_name').focus(); } async function saveUser() { const id = document.getElementById('u_id').value; - if (!id) return alert('Выберите пользователя'); const expDate = document.getElementById('u_exp').value; if (!expDate) return alert('Выберите дату деактивации'); const payload = { @@ -997,8 +924,16 @@ active: document.getElementById('u_active').value === 'true', is_admin: document.getElementById('u_admin').value === 'true', }; - await api(`/api/admin/users/${id}`, 'PUT', payload); - location.reload(); + if (id) { + await api(`/api/admin/users/${id}`, 'PUT', payload); + location.reload(); + } else { + const result = await api('/api/admin/users', 'POST', payload); + if (result.password) { + alert(`Письмо не отправлено (${result.email_status}). Пароль: ${result.password}\nПередайте его пользователю вручную.`); + } + location.reload(); + } } async function resetUserPassword() { @@ -1022,14 +957,23 @@ } function clearUserForm() { - ['u_id','u_name','u_exp'].forEach(id => document.getElementById(id).value = ''); + ['u_id','u_name','u_first_name','u_last_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.getElementById('user_delete_btn').style.display = 'none'; + document.getElementById('user_acl_box').style.display = 'none'; + document.getElementById('user_pwd_help').style.display = ''; + document.getElementById('user_form_title').textContent = 'Новый пользователь'; document.querySelectorAll('.acl_service').forEach(x => x.checked = false); document.querySelectorAll('.user-item').forEach((el) => el.classList.remove('selected-item')); } + async function toggleUserActive(id, nextActive) { + await api(`/api/admin/users/${id}`, 'PUT', {active: nextActive}); + location.reload(); + } + function syncAclForSelectedUser() { const userId = parseInt(document.getElementById('u_id').value || '0', 10); const allowed = new Set((aclMap[userId] || [])); @@ -1062,38 +1006,28 @@ document.getElementById('w_icon_preview').src = iconPath || placeholderIcon; setCategoryChecks('.w_cat', serviceCategoryMap[id] || []); document.getElementById('w_health_box').style.display = 'block'; + document.getElementById('w_icon_box').style.display = ''; + document.getElementById('w_delete_btn').style.display = ''; + document.getElementById('web_form_title').textContent = `WEB: ${name}`; markSelected('.web-item', 'data-service-id', id); refreshSelectedServiceStatus('web'); } + function startNewWeb() { + clearWebForm(); + document.getElementById('w_name').focus(); + } + async function saveWebPoolSize() { const size = parseInt(document.getElementById('web_pool_size').value || '0', 10); await api('/api/admin/web-pool-size', 'PUT', {size}); location.reload(); } - async function createWebService() { - const slug = document.getElementById('new_w_slug').value || slugifyRu(document.getElementById('new_w_name').value); - await api('/api/admin/services', 'POST', { - name: document.getElementById('new_w_name').value, - slug, - type: 'WEB', - target: document.getElementById('new_w_target').value, - comment: document.getElementById('new_w_comment').value, - svc_login: document.getElementById('new_w_svc_login').value, - svc_password: document.getElementById('new_w_svc_password').value, - svc_cred_hint: document.getElementById('new_w_svc_cred_hint').value, - category_ids: checkedCategoryIds('.new_w_cat'), - active: document.getElementById('new_w_active').value === 'true', - }); - location.reload(); - } - async function saveWebService() { const id = document.getElementById('w_id').value; - if (!id) return alert('Выберите WEB сервис'); const slug = document.getElementById('w_slug').value || slugifyRu(document.getElementById('w_name').value); - await api(`/api/admin/services/${id}`, 'PUT', { + const payload = { name: document.getElementById('w_name').value, slug, type: 'WEB', @@ -1104,7 +1038,12 @@ svc_cred_hint: document.getElementById('w_svc_cred_hint').value, category_ids: checkedCategoryIds('.w_cat'), active: document.getElementById('w_active').value === 'true', - }); + }; + if (id) { + await api(`/api/admin/services/${id}`, 'PUT', payload); + } else { + await api('/api/admin/services', 'POST', payload); + } location.reload(); } @@ -1114,6 +1053,9 @@ setCategoryChecks('.w_cat', []); document.getElementById('w_icon_preview').src = placeholderIcon; document.getElementById('w_health_box').style.display = 'none'; + document.getElementById('w_icon_box').style.display = 'none'; + document.getElementById('w_delete_btn').style.display = 'none'; + document.getElementById('web_form_title').textContent = 'Новый WEB-сервис'; document.querySelectorAll('.web-item').forEach((el) => el.classList.remove('selected-item')); } @@ -1211,37 +1153,26 @@ setCategoryChecks('.r_cat', serviceCategoryMap[id] || []); document.getElementById('r_icon_preview').src = iconPath || placeholderIcon; document.getElementById('r_health_box').style.display = 'block'; + document.getElementById('r_icon_box').style.display = ''; + document.getElementById('r_delete_btn').style.display = ''; + document.getElementById('r_prewarm_btn').style.display = ''; + document.getElementById('rdp_form_title').textContent = `RDP: ${name}`; markSelected('.rdp-item', 'data-service-id', id); renderRdpSlots(id); refreshSelectedServiceStatus('rdp'); } - async function createRdpService() { - const slug = document.getElementById('new_r_slug').value || slugifyRu(document.getElementById('new_r_name').value); - const target = buildRdpTarget('new_r'); - await api('/api/admin/services', 'POST', { - name: document.getElementById('new_r_name').value, - slug, - type: 'RDP', - target, - comment: document.getElementById('new_r_comment').value, - svc_login: document.getElementById('new_r_svc_login').value, - svc_password: document.getElementById('new_r_svc_password').value, - svc_cred_hint: document.getElementById('new_r_svc_cred_hint').value, - category_ids: checkedCategoryIds('.new_r_cat'), - warm_pool_size: parseInt(document.getElementById('new_r_pool').value || '0', 10), - active: document.getElementById('new_r_active').value === 'true', - }); - location.reload(); + function startNewRdp() { + clearRdpForm(); + document.getElementById('r_name').focus(); } async function saveRdpService() { const id = document.getElementById('r_id').value; - if (!id) return alert('Выберите RDP сервис'); const target = buildRdpTarget('r'); - await api(`/api/admin/services/${id}`, 'PUT', { + const payload = { name: document.getElementById('r_name').value, - slug: document.getElementById('r_slug').value, + slug: document.getElementById('r_slug').value || slugifyRu(document.getElementById('r_name').value), type: 'RDP', target, comment: document.getElementById('r_comment').value, @@ -1251,7 +1182,12 @@ category_ids: checkedCategoryIds('.r_cat'), warm_pool_size: parseInt(document.getElementById('r_pool').value || '0', 10), active: document.getElementById('r_active').value === 'true', - }); + }; + if (id) { + await api(`/api/admin/services/${id}`, 'PUT', payload); + } else { + await api('/api/admin/services', 'POST', payload); + } location.reload(); } @@ -1263,6 +1199,10 @@ setCategoryChecks('.r_cat', []); document.getElementById('r_icon_preview').src = placeholderIcon; document.getElementById('r_health_box').style.display = 'none'; + document.getElementById('r_icon_box').style.display = 'none'; + document.getElementById('r_delete_btn').style.display = 'none'; + document.getElementById('r_prewarm_btn').style.display = 'none'; + document.getElementById('rdp_form_title').textContent = 'Новый RDP-сервис'; document.querySelectorAll('.rdp-item').forEach((el) => el.classList.remove('selected-item')); } @@ -1340,7 +1280,7 @@ document.getElementById('p_name').focus(); } - async function togglePilotActive(id, nextActive) { + async function toggleServiceActive(id, nextActive) { await api(`/api/admin/services/${id}`, 'PUT', {active: nextActive}); location.reload(); }