Files
minicrm/public/doc.html
2026-02-07 22:28:36 +05:00

961 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Панель управления - Группы и идентификаторы</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
:root {
--primary: #3b82f6;
--primary-dark: #2563eb;
--primary-light: #60a5fa;
--secondary: #6b7280;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--info: #06b6d4;
--light: #f9fafb;
--dark: #111827;
--border: #e5e7eb;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--radius: 8px;
}
body {
background-color: #f3f4f6;
color: var(--dark);
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
margin-bottom: 30px;
border-bottom: 2px solid var(--border);
}
.logo {
font-size: 24px;
font-weight: 700;
color: var(--primary);
display: flex;
align-items: center;
gap: 10px;
}
.user-info {
display: flex;
align-items: center;
gap: 20px;
}
.user-details {
text-align: right;
}
.user-name {
font-weight: 600;
color: var(--dark);
}
.user-role {
font-size: 14px;
color: var(--secondary);
}
.btn-logout {
background-color: var(--danger);
color: white;
border: none;
padding: 8px 16px;
border-radius: var(--radius);
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.btn-logout:hover {
background-color: #dc2626;
}
/* Tabs */
.tabs {
display: flex;
border-bottom: 2px solid var(--border);
margin-bottom: 30px;
gap: 5px;
}
.tab {
padding: 12px 24px;
background: none;
border: none;
border-bottom: 3px solid transparent;
font-size: 16px;
font-weight: 500;
color: var(--secondary);
cursor: pointer;
transition: all 0.3s;
border-radius: var(--radius var(--radius) 0 0);
}
.tab:hover {
color: var(--primary);
background-color: #f0f9ff;
}
.tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
background-color: #f0f9ff;
}
/* Tab Content */
.tab-content {
display: none;
animation: fadeIn 0.5s;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Toolbar */
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.search-container {
flex: 1;
min-width: 300px;
max-width: 500px;
}
.search-input {
width: 100%;
padding: 10px 15px;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 16px;
transition: border-color 0.3s;
}
.search-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-select {
padding: 10px 15px;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 14px;
background-color: white;
min-width: 150px;
}
.actions {
display: flex;
gap: 10px;
}
/* Buttons */
.btn {
padding: 10px 20px;
border: none;
border-radius: var(--radius);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-dark);
}
.btn-success {
background-color: var(--success);
color: white;
}
.btn-success:hover {
background-color: #0da271;
}
.btn-danger {
background-color: var(--danger);
color: white;
}
.btn-danger:hover {
background-color: #dc2626;
}
.btn-small {
padding: 6px 12px;
font-size: 13px;
}
/* Tables */
.table-container {
background-color: white;
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 30px;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #f8fafc;
padding: 15px;
text-align: left;
font-weight: 600;
color: var(--dark);
border-bottom: 2px solid var(--border);
}
td {
padding: 15px;
border-bottom: 1px solid var(--border);
}
tr:hover {
background-color: #f9fafb;
}
.loading, .error, .no-data {
text-align: center;
padding: 40px !important;
color: var(--secondary);
}
.loading i {
margin-right: 10px;
}
.error {
color: var(--danger);
}
/* Badges */
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge-sberbank {
background-color: #048b46;
color: white;
}
.badge-yandex {
background-color: #fc3f1d;
color: white;
}
.badge-ldap {
background-color: #3b82f6;
color: white;
}
.badge-other {
background-color: #6b7280;
color: white;
}
/* Status */
.status-active {
color: var(--success);
font-weight: 600;
}
.status-inactive {
color: var(--danger);
font-weight: 600;
}
/* Metadata Preview */
.metadata-preview {
background-color: #f0f9ff;
border: 1px solid var(--primary-light);
border-radius: var(--radius);
padding: 5px 10px;
font-size: 12px;
color: var(--primary);
cursor: pointer;
transition: background-color 0.3s;
text-align: center;
}
.metadata-preview:hover {
background-color: #dbeafe;
}
/* Pagination */
.pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
gap: 5px;
}
.page-btn {
padding: 8px 14px;
border: 1px solid var(--border);
background-color: white;
border-radius: var(--radius);
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
}
.page-btn:hover:not(:disabled) {
background-color: #f3f4f6;
border-color: var(--primary);
}
.page-btn.active {
background-color: var(--primary);
color: white;
border-color: var(--primary);
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Modals */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
animation: fadeIn 0.3s;
}
.modal.active {
display: flex;
}
.modal-content {
background-color: white;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
animation: slideIn 0.3s;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-50px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-header {
padding: 20px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 20px;
font-weight: 600;
color: var(--dark);
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: var(--secondary);
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.3s;
}
.modal-close:hover {
background-color: #f3f4f6;
}
.modal-body {
padding: 20px;
}
.modal-footer {
padding: 20px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* Forms */
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--dark);
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 16px;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
textarea.form-control {
min-height: 100px;
resize: vertical;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
}
.message {
padding: 15px;
border-radius: var(--radius);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.success {
background-color: #d1fae5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.error {
background-color: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
/* Stats */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: white;
border-radius: var(--radius);
padding: 20px;
box-shadow: var(--shadow);
text-align: center;
}
.stat-label {
font-size: 14px;
color: var(--secondary);
margin-bottom: 10px;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: var(--primary);
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.user-info {
flex-direction: column;
text-align: center;
}
.toolbar {
flex-direction: column;
align-items: stretch;
}
.search-container {
max-width: 100%;
}
.filters {
width: 100%;
}
.filter-select {
flex: 1;
min-width: 0;
}
.actions {
width: 100%;
justify-content: center;
}
.table-container {
overflow-x: auto;
}
table {
min-width: 800px;
}
.modal-content {
width: 95%;
}
.form-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<div class="logo">
<i class="fas fa-users-cog"></i>
<span>Панель управления</span>
</div>
<div class="user-info">
<div class="user-details">
<div class="user-name" id="userName">Загрузка...</div>
<div class="user-role" id="userRole">Роль: загрузка...</div>
</div>
<button class="btn-logout" onclick="logout()">
<i class="fas fa-sign-out-alt"></i>
Выйти
</button>
</div>
</header>
<!-- Tabs -->
<div class="tabs">
<button class="tab active" data-tab="groups">
<i class="fas fa-layer-group"></i>
Группы
</button>
<button class="tab" data-tab="idusers">
<i class="fas fa-id-card"></i>
Идентификаторы
</button>
<button class="tab" data-tab="stats">
<i class="fas fa-chart-bar"></i>
Статистика
</button>
</div>
<!-- Tab: Groups -->
<div id="tab-groups" class="tab-content active">
<div class="toolbar">
<div class="search-container">
<input type="text" id="groupSearch" class="search-input" placeholder="Поиск по названию или описанию...">
</div>
<div class="filters">
<select id="groupServiceTypeFilter" class="filter-select">
<option value="">Все типы сервисов</option>
<option value="sberbank">Сбербанк</option>
<option value="yandex">Яндекс</option>
<option value="ldap">LDAP</option>
<option value="other">Прочие</option>
</select>
<select id="groupStatusFilter" class="filter-select">
<option value="">Все статусы</option>
<option value="true">Активные</option>
<option value="false">Неактивные</option>
</select>
</div>
<div class="actions">
<button class="btn btn-success" onclick="showAddGroupModal()">
<i class="fas fa-plus"></i>
Добавить группу
</button>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Тип сервиса</th>
<th>Описание</th>
<th>Статус</th>
<th>Дата создания</th>
<th>Дата обновления</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="groupsTableBody">
<tr>
<td colspan="8" class="loading">
<i class="fas fa-spinner fa-spin"></i>
Загрузка групп...
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination" id="groupsPagination"></div>
</div>
<!-- Tab: IdUsers -->
<div id="tab-idusers" class="tab-content">
<div class="toolbar">
<div class="search-container">
<input type="text" id="iduserSearch" class="search-input" placeholder="Поиск по ID, логину или имени...">
</div>
<div class="filters">
<select id="iduserServiceTypeFilter" class="filter-select">
<option value="">Все типы сервисов</option>
<option value="sberbank">Сбербанк</option>
<option value="yandex">Яндекс</option>
<option value="ldap">LDAP</option>
<option value="other">Прочие</option>
</select>
<select id="iduserGroupFilter" class="filter-select">
<option value="">Все группы</option>
<!-- Динамически заполняется -->
</select>
<select id="iduserStatusFilter" class="filter-select">
<option value="">Все статусы</option>
<option value="true">Активные</option>
<option value="false">Неактивные</option>
</select>
</div>
<div class="actions">
<button class="btn btn-success" onclick="showAddIdUserModal()">
<i class="fas fa-plus"></i>
Добавить идентификатор
</button>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>ID</th>
<th>Пользователь</th>
<th>Тип сервиса</th>
<th>Внешний ID</th>
<th>Логин</th>
<th>Группа LDAP</th>
<th>Группа</th>
<th>Метаданные</th>
<th>Статус</th>
<th>Дата создания</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="idusersTableBody">
<tr>
<td colspan="11" class="loading">
<i class="fas fa-spinner fa-spin"></i>
Загрузка идентификаторов...
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination" id="idusersPagination"></div>
</div>
<!-- Tab: Stats -->
<div id="tab-stats" class="tab-content">
<div class="stats-grid" id="statsGrid">
<div class="loading">
<i class="fas fa-spinner fa-spin"></i>
Загрузка статистики...
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Тип сервиса</th>
<th>Всего идентификаторов</th>
<th>Активных</th>
<th>Уникальных пользователей</th>
<th>Доля</th>
</tr>
</thead>
<tbody id="statsTableBody">
<tr>
<td colspan="5" class="loading">
<i class="fas fa-spinner fa-spin"></i>
Загрузка данных...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal: Add/Edit Group -->
<div id="groupModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="groupModalTitle">Добавить группу</h3>
<button class="modal-close" onclick="closeGroupModal()">&times;</button>
</div>
<form id="groupForm" onsubmit="saveGroup(event)">
<div class="modal-body">
<div id="groupMessage"></div>
<input type="hidden" id="groupId" value="">
<div class="form-grid">
<div class="form-group">
<label for="groupName">Название группы *</label>
<input type="text" id="groupName" class="form-control" required>
</div>
<div class="form-group">
<label for="groupServiceType">Тип сервиса *</label>
<select id="groupServiceType" class="form-control" required>
<option value="sberbank">Сбербанк</option>
<option value="yandex">Яндекс</option>
<option value="ldap">LDAP</option>
<option value="other">Прочие</option>
</select>
</div>
<div class="form-group">
<label for="groupDescription">Описание</label>
<textarea id="groupDescription" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label for="groupIsActive" class="checkbox-group">
<input type="checkbox" id="groupIsActive" checked>
Активная группа
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeGroupModal()">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Modal: Add/Edit IdUser -->
<div id="iduserModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="iduserModalTitle">Добавить идентификатор</h3>
<button class="modal-close" onclick="closeIdUserModal()">&times;</button>
</div>
<form id="iduserForm" onsubmit="saveIdUser(event)">
<div class="modal-body">
<div id="iduserMessage"></div>
<input type="hidden" id="iduserId" value="">
<div class="form-grid">
<div class="form-group">
<label for="iduserUserId">Пользователь *</label>
<select id="iduserUserId" class="form-control" required>
<option value="">Выберите пользователя</option>
<!-- Динамически заполняется -->
</select>
</div>
<div class="form-group">
<label for="iduserServiceType">Тип сервиса *</label>
<select id="iduserServiceType" class="form-control" required>
<option value="ldap" selected>LDAP</option>
<option value="sberbank">Сбербанк</option>
<option value="yandex">Яндекс</option>
<option value="other">Прочие</option>
</select>
</div>
<div class="form-group">
<label for="iduserExternalId">Внешний ID (необязательный)</label>
<input type="text" id="iduserExternalId" class="form-control" placeholder="Внешний идентификатор (необязательно)">
</div>
<div class="form-group">
<label for="iduserLogin">Логин</label>
<input type="text" id="iduserLogin" class="form-control" placeholder="Логин пользователя">
</div>
<div class="form-group">
<label for="iduserLdapGroup">Группа LDAP</label>
<input type="text" id="iduserLdapGroup" class="form-control" placeholder="Группа LDAP">
</div>
<div class="form-group">
<label for="iduserGroupId">Группа идентификаторов</label>
<select id="iduserGroupId" class="form-control">
<option value="">Без группы</option>
<!-- Динамически заполняется -->
</select>
</div>
<div class="form-group">
<label for="iduserIsActive" class="checkbox-group">
<input type="checkbox" id="iduserIsActive" checked>
Активный идентификатор
</label>
</div>
<div class="form-group" style="grid-column: span 2;">
<label for="iduserMetadata">Метаданные (JSON)</label>
<textarea id="iduserMetadata" class="form-control" rows="6" placeholder='{"ключ": "значение"}'></textarea>
<small style="color: var(--secondary); margin-top: 5px; display: block;">
Дополнительные данные в формате JSON (необязательно)
</small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeIdUserModal()">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Modal: Confirm Delete -->
<div id="confirmModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="confirmModalTitle">Подтверждение удаления</h3>
<button class="modal-close" onclick="closeConfirmModal()">&times;</button>
</div>
<div class="modal-body">
<p id="confirmMessage">Вы уверены, что хотите удалить этот элемент?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeConfirmModal()">Отмена</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Удалить</button>
</div>
</div>
</div>
<!-- Modal: View Metadata -->
<div id="metadataModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Метаданные</h3>
<button class="modal-close" onclick="closeMetadataModal()">&times;</button>
</div>
<div class="modal-body">
<pre id="metadataContent" style="background: #f8fafc; padding: 15px; border-radius: var(--radius); overflow: auto; max-height: 500px; font-family: monospace;"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeMetadataModal()">Закрыть</button>
</div>
</div>
</div>
<script src="doc.js"></script>
</body>
</html>