Added Role Owner and new UI for Owner
This commit is contained in:
327
frontend/src/components/UserManagement.jsx
Normal file
327
frontend/src/components/UserManagement.jsx
Normal file
@@ -0,0 +1,327 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Users, Shield, Ban, Trash2, UserCheck, Server, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
|
||||
const UserManagement = ({ currentUser, addNotification }) => {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
const [showRoleModal, setShowRoleModal] = useState(false);
|
||||
const [showAccessModal, setShowAccessModal] = useState(false);
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
||||
|
||||
// Загрузка пользователей
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.get(`${API_URL}/api/users`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
// Преобразуем объект в массив если нужно
|
||||
const usersData = Array.isArray(response.data)
|
||||
? response.data
|
||||
: Object.values(response.data);
|
||||
|
||||
setUsers(usersData);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки пользователей:', error);
|
||||
addNotification('error', 'Ошибка загрузки пользователей');
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
// Изменить роль
|
||||
const changeRole = async (username, newRole) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.put(
|
||||
`${API_URL}/api/users/${username}/role`,
|
||||
{ role: newRole },
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
|
||||
addNotification('success', `Роль пользователя ${username} изменена на ${newRole}`);
|
||||
loadUsers();
|
||||
setShowRoleModal(false);
|
||||
} catch (error) {
|
||||
console.error('Ошибка изменения роли:', error);
|
||||
addNotification('error', error.response?.data?.detail || 'Ошибка изменения роли');
|
||||
}
|
||||
};
|
||||
|
||||
// Заблокировать пользователя
|
||||
const banUser = async (username) => {
|
||||
if (!confirm(`Заблокировать пользователя ${username}?`)) return;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.post(
|
||||
`${API_URL}/api/users/${username}/ban`,
|
||||
{ reason: 'Заблокирован администратором' },
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
|
||||
addNotification('success', `Пользователь ${username} заблокирован`);
|
||||
loadUsers();
|
||||
} catch (error) {
|
||||
console.error('Ошибка блокировки:', error);
|
||||
addNotification('error', error.response?.data?.detail || 'Ошибка блокировки');
|
||||
}
|
||||
};
|
||||
|
||||
// Разблокировать пользователя
|
||||
const unbanUser = async (username) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.post(
|
||||
`${API_URL}/api/users/${username}/unban`,
|
||||
{},
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
|
||||
addNotification('success', `Пользователь ${username} разблокирован`);
|
||||
loadUsers();
|
||||
} catch (error) {
|
||||
console.error('Ошибка разблокировки:', error);
|
||||
addNotification('error', error.response?.data?.detail || 'Ошибка разблокировки');
|
||||
}
|
||||
};
|
||||
|
||||
// Удалить пользователя
|
||||
const deleteUser = async (username) => {
|
||||
if (!confirm(`Удалить пользователя ${username}? Это действие необратимо!`)) return;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.delete(
|
||||
`${API_URL}/api/users/${username}`,
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
|
||||
addNotification('success', `Пользователь ${username} удалён`);
|
||||
loadUsers();
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления:', error);
|
||||
addNotification('error', error.response?.data?.detail || 'Ошибка удаления');
|
||||
}
|
||||
};
|
||||
|
||||
// Цвета ролей
|
||||
const getRoleColor = (role) => {
|
||||
switch (role) {
|
||||
case 'owner': return 'text-yellow-400';
|
||||
case 'admin': return 'text-red-400';
|
||||
case 'support': return 'text-blue-400';
|
||||
case 'user': return 'text-green-400';
|
||||
case 'banned': return 'text-gray-400';
|
||||
default: return 'text-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleName = (role) => {
|
||||
switch (role) {
|
||||
case 'owner': return 'Владелец';
|
||||
case 'admin': return 'Администратор';
|
||||
case 'support': return 'Поддержка';
|
||||
case 'user': return 'Пользователь';
|
||||
case 'banned': return 'Заблокирован';
|
||||
default: return role;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-400">Загрузка пользователей...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="w-8 h-8 text-blue-400" />
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white">Управление пользователями</h2>
|
||||
<p className="text-gray-400">Всего пользователей: {users.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список пользователей */}
|
||||
<div className="grid gap-4">
|
||||
{users.map((user) => (
|
||||
<div
|
||||
key={user.username}
|
||||
className="bg-gray-800 rounded-lg p-4 border border-gray-700 hover:border-gray-600 transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Информация о пользователе */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gray-700 rounded-full flex items-center justify-center">
|
||||
<Users className="w-6 h-6 text-gray-400" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-semibold text-white">{user.username}</h3>
|
||||
{user.role === 'owner' && (
|
||||
<span className="px-2 py-1 bg-yellow-500/20 text-yellow-400 text-xs rounded">
|
||||
👑 Владелец
|
||||
</span>
|
||||
)}
|
||||
{user.role === 'admin' && (
|
||||
<span className="px-2 py-1 bg-red-500/20 text-red-400 text-xs rounded">
|
||||
🛡️ Админ
|
||||
</span>
|
||||
)}
|
||||
{user.role === 'support' && (
|
||||
<span className="px-2 py-1 bg-blue-500/20 text-blue-400 text-xs rounded">
|
||||
💬 Поддержка
|
||||
</span>
|
||||
)}
|
||||
{user.role === 'banned' && (
|
||||
<span className="px-2 py-1 bg-gray-500/20 text-gray-400 text-xs rounded">
|
||||
🚫 Заблокирован
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mt-1 text-sm text-gray-400">
|
||||
<span className={getRoleColor(user.role)}>
|
||||
{getRoleName(user.role)}
|
||||
</span>
|
||||
{user.resource_access?.servers && user.resource_access.servers.length > 0 && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Server className="w-4 h-4" />
|
||||
{user.resource_access.servers.length} серверов
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Права */}
|
||||
{user.permissions && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{user.permissions.manage_users && (
|
||||
<span className="px-2 py-0.5 bg-green-500/20 text-green-400 text-xs rounded">
|
||||
Управление пользователями
|
||||
</span>
|
||||
)}
|
||||
{user.permissions.manage_servers && (
|
||||
<span className="px-2 py-0.5 bg-blue-500/20 text-blue-400 text-xs rounded">
|
||||
Управление серверами
|
||||
</span>
|
||||
)}
|
||||
{user.permissions.view_all_resources && (
|
||||
<span className="px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded">
|
||||
Просмотр всех ресурсов
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Действия */}
|
||||
{currentUser.role === 'owner' && user.username !== currentUser.username && (
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Изменить роль */}
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedUser(user);
|
||||
setShowRoleModal(true);
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded transition-colors flex items-center gap-2"
|
||||
title="Изменить роль"
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
Роль
|
||||
</button>
|
||||
|
||||
{/* Заблокировать/Разблокировать */}
|
||||
{user.role !== 'banned' ? (
|
||||
<button
|
||||
onClick={() => banUser(user.username)}
|
||||
className="px-3 py-2 bg-orange-500 hover:bg-orange-600 text-white rounded transition-colors flex items-center gap-2"
|
||||
title="Заблокировать"
|
||||
>
|
||||
<Ban className="w-4 h-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => unbanUser(user.username)}
|
||||
className="px-3 py-2 bg-green-500 hover:bg-green-600 text-white rounded transition-colors flex items-center gap-2"
|
||||
title="Разблокировать"
|
||||
>
|
||||
<UserCheck className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Удалить */}
|
||||
<button
|
||||
onClick={() => deleteUser(user.username)}
|
||||
className="px-3 py-2 bg-red-500 hover:bg-red-600 text-white rounded transition-colors flex items-center gap-2"
|
||||
title="Удалить"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Модальное окно изменения роли */}
|
||||
{showRoleModal && selectedUser && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-gray-800 rounded-lg p-6 w-96 border border-gray-700">
|
||||
<h3 className="text-xl font-bold text-white mb-4">
|
||||
Изменить роль: {selectedUser.username}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
{['owner', 'admin', 'support', 'user', 'banned'].map((role) => (
|
||||
<button
|
||||
key={role}
|
||||
onClick={() => changeRole(selectedUser.username, role)}
|
||||
className={`w-full px-4 py-3 rounded text-left transition-colors ${
|
||||
selectedUser.role === role
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">{getRoleName(role)}</div>
|
||||
<div className="text-sm opacity-75">
|
||||
{role === 'owner' && 'Полный контроль над панелью'}
|
||||
{role === 'admin' && 'Управление панелью без изменения ролей'}
|
||||
{role === 'support' && 'Работа с тикетами поддержки'}
|
||||
{role === 'user' && 'Базовые возможности'}
|
||||
{role === 'banned' && 'Доступ заблокирован'}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setShowRoleModal(false)}
|
||||
className="w-full mt-4 px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserManagement;
|
||||
Reference in New Issue
Block a user