200 lines
7.1 KiB
JavaScript
200 lines
7.1 KiB
JavaScript
import { useState, useEffect } from 'react';
|
||
import { Users as UsersIcon, Trash2, Shield, User } from 'lucide-react';
|
||
import axios from 'axios';
|
||
import { API_URL } from '../config';
|
||
|
||
export default function Users({ token, onViewProfile }) {
|
||
const [users, setUsers] = useState([]);
|
||
const [servers, setServers] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
loadData();
|
||
}, []);
|
||
|
||
const loadData = async () => {
|
||
try {
|
||
const [usersRes, serversRes] = await Promise.all([
|
||
axios.get(`${API_URL}/api/users`, {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
}),
|
||
axios.get(`${API_URL}/api/servers`, {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
})
|
||
]);
|
||
setUsers(usersRes.data);
|
||
setServers(serversRes.data);
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки данных:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const toggleServerAccess = async (username, serverName) => {
|
||
const user = users.find(u => u.username === username);
|
||
const currentServers = user.servers || [];
|
||
const newServers = currentServers.includes(serverName)
|
||
? currentServers.filter(s => s !== serverName)
|
||
: [...currentServers, serverName];
|
||
|
||
try {
|
||
await axios.put(
|
||
`${API_URL}/api/users/${username}/servers`,
|
||
{ servers: newServers },
|
||
{ headers: { Authorization: `Bearer ${token}` } }
|
||
);
|
||
loadData();
|
||
} catch (error) {
|
||
alert(error.response?.data?.detail || 'Ошибка обновления доступа');
|
||
}
|
||
};
|
||
|
||
const changeRole = async (username, newRole) => {
|
||
try {
|
||
await axios.put(
|
||
`${API_URL}/api/users/${username}/role`,
|
||
{ role: newRole },
|
||
{ headers: { Authorization: `Bearer ${token}` } }
|
||
);
|
||
loadData();
|
||
} catch (error) {
|
||
alert(error.response?.data?.detail || 'Ошибка изменения роли');
|
||
}
|
||
};
|
||
|
||
const deleteUser = async (username) => {
|
||
if (!confirm(`Удалить пользователя ${username}?`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await axios.delete(`${API_URL}/api/users/${username}`, {
|
||
headers: { Authorization: `Bearer ${token}` }
|
||
});
|
||
loadData();
|
||
} catch (error) {
|
||
alert(error.response?.data?.detail || 'Ошибка удаления пользователя');
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-full">
|
||
<div className="text-gray-400">Загрузка...</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="p-8 bg-gray-900 h-full overflow-y-auto">
|
||
<h2 className="text-2xl font-bold mb-6 flex items-center gap-2">
|
||
<UsersIcon className="w-8 h-8" />
|
||
Управление пользователями
|
||
</h2>
|
||
|
||
<div className="space-y-4">
|
||
{users.map((user) => (
|
||
<div
|
||
key={user.username}
|
||
className="bg-gray-800 rounded-lg p-6 border border-gray-700"
|
||
>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className={`p-2 rounded ${
|
||
user.role === 'admin' ? 'bg-blue-600' : user.role === 'support' ? 'bg-purple-600' : user.role === 'banned' ? 'bg-red-600' : 'bg-gray-700'
|
||
}`}>
|
||
{user.role === 'admin' ? (
|
||
<Shield className="w-6 h-6" />
|
||
) : (
|
||
<User className="w-6 h-6" />
|
||
)}
|
||
</div>
|
||
<div>
|
||
<button
|
||
onClick={() => onViewProfile && onViewProfile(user.username)}
|
||
className="text-lg font-semibold hover:text-blue-400 transition cursor-pointer text-left"
|
||
title="Просмотреть профиль"
|
||
>
|
||
{user.username}
|
||
</button>
|
||
<p className="text-sm text-gray-400">
|
||
{user.role === 'admin' ? 'Администратор' : user.role === 'support' ? 'Тех. поддержка' : user.role === 'banned' ? 'Забанен' : 'Пользователь'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<select
|
||
value={user.role}
|
||
onChange={(e) => changeRole(user.username, e.target.value)}
|
||
className="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded text-sm border border-gray-600 focus:outline-none focus:border-blue-500"
|
||
>
|
||
<option value="user">Пользователь</option>
|
||
<option value="support">Тех. поддержка</option>
|
||
<option value="admin">Администратор</option>
|
||
<option value="banned">Забанен</option>
|
||
</select>
|
||
<button
|
||
onClick={() => deleteUser(user.username)}
|
||
className="bg-red-600 hover:bg-red-700 p-2 rounded"
|
||
title="Удалить"
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{user.role === 'user' && (
|
||
<div>
|
||
<h4 className="text-sm font-medium mb-2 text-gray-400">
|
||
Доступ к серверам:
|
||
</h4>
|
||
<div className="flex flex-wrap gap-2">
|
||
{servers.map((server) => {
|
||
const hasAccess = user.servers?.includes(server.name);
|
||
return (
|
||
<button
|
||
key={server.name}
|
||
onClick={() => toggleServerAccess(user.username, server.name)}
|
||
className={`px-3 py-1 rounded text-sm transition ${
|
||
hasAccess
|
||
? 'bg-green-600 hover:bg-green-700'
|
||
: 'bg-gray-700 hover:bg-gray-600'
|
||
}`}
|
||
>
|
||
{server.displayName}
|
||
</button>
|
||
);
|
||
})}
|
||
{servers.length === 0 && (
|
||
<p className="text-gray-500 text-sm">Нет доступных серверов</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{user.role === 'admin' && (
|
||
<p className="text-sm text-gray-400">
|
||
Администратор имеет доступ ко всем серверам
|
||
</p>
|
||
)}
|
||
|
||
{user.role === 'support' && (
|
||
<p className="text-sm text-gray-400">
|
||
Тех. поддержка имеет доступ к системе тикетов
|
||
</p>
|
||
)}
|
||
|
||
{user.role === 'banned' && (
|
||
<p className="text-sm text-red-400">
|
||
⛔ Пользователь заблокирован и не имеет доступа к панели
|
||
</p>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|